import re
from datetime import datetime, timezone
from pathlib import Path

import pytest

from slixmpp import JID

from slidge.contact import LegacyContact
from slidge.core import config
from slidge.core.mixins.presence import get_last_seen_fallback
from slidge.util import (
    SubclassableOnce,
    is_valid_phone_number,
)
from slidge.util.types import Mention, LegacyAttachment
from slidge.util.util import merge_resources, replace_mentions, strip_leading_emoji


def test_subclass():
    SubclassableOnce.TEST_MODE = False
    # fmt: off
    class A(SubclassableOnce): pass

    with pytest.raises(AttributeError):
        A.get_unique_subclass()

    assert A.get_self_or_unique_subclass() is A

    class B(A): pass
    assert A.get_self_or_unique_subclass() is B

    with pytest.raises(AttributeError):
        B.get_unique_subclass()

    with pytest.raises(RuntimeError):
        class C(A): pass

    A.reset_subclass()

    class C(A): pass
    assert A.get_self_or_unique_subclass() is C

    A.reset_subclass()
    class D(SubclassableOnce): pass

    assert D.get_self_or_unique_subclass() is D

    # fmt: on
    SubclassableOnce.TEST_MODE = True


def test_phone_validation():
    assert is_valid_phone_number("+33")
    assert not is_valid_phone_number("+")
    assert not is_valid_phone_number("+asdfsadfa48919sadf")
    assert not is_valid_phone_number("12597891")


def test_strip_delay(monkeypatch):
    monkeypatch.setattr(config, "IGNORE_DELAY_THRESHOLD", 300)

    class MockDelay:
        @staticmethod
        def set_stamp(x):
            pass

        @staticmethod
        def set_from(x):
            pass

    class MockC:
        STRIP_SHORT_DELAY = True

        class xmpp:
            boundjid = JID("test")

    class MockMsg:
        delay_added = None

        def __getitem__(self, key):
            if key == "delay":
                self.delay_added = True
            return MockDelay

    msg = MockMsg()
    LegacyContact._add_delay(MockC, msg, datetime.now())
    assert not msg.delay_added

    monkeypatch.setattr(config, "IGNORE_DELAY_THRESHOLD", 0)

    msg = MockMsg()
    LegacyContact._add_delay(MockC, msg, datetime.now())
    assert msg.delay_added


def test_merge_presence():
    assert merge_resources(
        {
            "1": {
                "show": "",
                "status": "",
                "priority": 0,
            }
        }
    ) == {
        "show": "",
        "status": "",
        "priority": 0,
    }

    assert merge_resources(
        {
            "1": {
                "show": "dnd",
                "status": "X",
                "priority": -10,
            },
            "2": {
                "show": "dnd",
                "status": "",
                "priority": 0,
            },
        }
    ) == {
        "show": "dnd",
        "status": "X",
        "priority": 0,
    }

    assert merge_resources(
        {
            "1": {
                "show": "",
                "status": "",
                "priority": 0,
            },
            "2": {
                "show": "away",
                "status": "",
                "priority": 0,
            },
            "3": {
                "show": "dnd",
                "status": "",
                "priority": 0,
            },
        }
    ) == {
        "show": "",
        "status": "",
        "priority": 0,
    }

    assert merge_resources(
        {
            "1": {
                "show": "",
                "status": "",
                "priority": 0,
            },
            "2": {
                "show": "away",
                "status": "",
                "priority": 0,
            },
            "3": {
                "show": "dnd",
                "status": "Blah blah",
                "priority": 0,
            },
        }
    ) == {
        "show": "",
        "status": "Blah blah",
        "priority": 0,
    }

    assert merge_resources(
        {
            "1": {
                "show": "",
                "status": "",
                "priority": 0,
            },
            "2": {
                "show": "away",
                "status": "Blah",
                "priority": 0,
            },
            "3": {
                "show": "dnd",
                "status": "Blah blah",
                "priority": 10,
            },
        }
    ) == {
        "show": "",
        "status": "Blah blah",
        "priority": 0,
    }

    assert merge_resources(
        {
            "1": {
                "show": "",
                "status": "",
                "priority": 0,
            },
            "2": {
                "show": "away",
                "status": "Blah",
                "priority": 0,
            },
            "3": {
                "show": "dnd",
                "status": "",
                "priority": 10,
            },
        }
    ) == {
        "show": "",
        "status": "Blah",
        "priority": 0,
    }


def test_replace_mentions():
    mentions = []
    text = "Text Mention 1 and Mention 2, and Mention 3"
    for match in re.finditer("Mention 1|Mention 2|Mention 3", text):
        span = match.span()
        nick = match.group()
        mentions.append(
            Mention(contact=f"Contact{nick[-1]}", start=span[0], end=span[1])
        )
    assert (
        replace_mentions(text, mentions, lambda c: "@" + c[-1])
        == "Text @1 and @2, and @3"
    )

    mentions = []
    text = "Text Mention 1 and Mention 2, and Mention 3 blabla"
    for match in re.finditer("Mention 1|Mention 2|Mention 3", text):
        span = match.span()
        nick = match.group()
        mentions.append(
            Mention(contact=f"Contact{nick[-1]}", start=span[0], end=span[1])
        )
    assert (
        replace_mentions(text, mentions, lambda c: "@" + c[-1])
        == "Text @1 and @2, and @3 blabla"
    )


def test_strip_emoji():
    try:
        import emoji
    except ImportError:
        return
    assert strip_leading_emoji("🛷️ Slidge administration") == "Slidge administration"
    assert strip_leading_emoji("no emoji") == "no emoji"
    assert strip_leading_emoji("no") == "no"
    assert strip_leading_emoji("👤 Contacts") == "Contacts"
    assert strip_leading_emoji("👥 Groups") == "Groups"


def test_last_seen_fallback_formatting():
    now = datetime.now(tz=timezone.utc)
    res, should_update = get_last_seen_fallback(now)
    assert should_update
    assert "AM" in res or "PM" in res


def test_attachment_str():
    att = LegacyAttachment(path="whatevs")
    assert str(att) == f"Attachment(path={Path('whatevs')!r})"

    att = LegacyAttachment(url="https://url", legacy_file_id="xxx")
    assert str(att) == "Attachment(legacy_file_id='xxx', url='https://url')"

    att = LegacyAttachment(data=b"aaaaa", legacy_file_id="xxx")
    assert str(att) == "Attachment(legacy_file_id='xxx', data=<5 bytes>)"

    att = LegacyAttachment(data=b"aaaaa")
    assert str(att) == "Attachment(data=<5 bytes>)"
