import uuid
from datetime import datetime, timezone
from typing import Any

import pytest
from pydantic import BaseModel, ValidationError

from pydantic_extra_types.ulid import ULID

try:
    from ulid import ULID as _ULID
except ModuleNotFoundError:  # pragma: no cover
    raise RuntimeError(
        'The `ulid` module requires "python-ulid" to be installed. You can install it with "pip install python-ulid".'
    )


class Something(BaseModel):
    ulid: ULID


@pytest.mark.parametrize(
    'ulid, result, valid',
    [
        # Valid ULID for str format
        ('01BTGNYV6HRNK8K8VKZASZCFPE', '01BTGNYV6HRNK8K8VKZASZCFPE', True),
        ('01BTGNYV6HRNK8K8VKZASZCFPF', '01BTGNYV6HRNK8K8VKZASZCFPF', True),
        # Invalid ULID for str format
        ('01BTGNYV6HRNK8K8VKZASZCFP', None, False),  # Invalid ULID (short length)
        ('01BTGNYV6HRNK8K8VKZASZCFPEA', None, False),  # Invalid ULID (long length)
        # Valid ULID for UUID format
        (uuid.UUID('0196FEB3-9C99-8D8C-B3F3-4301C5E9DCE1'), '01JVZB774SHP6B7WT3072YKQ71', True),
        (uuid.UUID('0196FEB3-CD14-4B50-0015-C1E09BF7B221'), '01JVZB7K8M9D8005E1W2DZFCH1', True),
        # Valid ULID for _ULID format
        (_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPE'), '01BTGNYV6HRNK8K8VKZASZCFPE', True),
        (_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPF'), '01BTGNYV6HRNK8K8VKZASZCFPF', True),
        # Invalid _ULID for bytes format
        (b'\x01\xba\x1e\xb2\x8a\x9f\xfay\x10\xd5\xa5k\xc8', None, False),  # Invalid ULID (short length)
        (b'\x01\xba\x1e\xb2\x8a\x9f\xfay\x10\xd5\xa5k\xc8\xb6\x00', None, False),  # Invalid ULID (long length)
        # Valid ULID for int format
        (109667145845879622871206540411193812282, '2JG4FVY7N8XS4GFVHPXGJZ8S9T', True),
        (109667145845879622871206540411193812283, '2JG4FVY7N8XS4GFVHPXGJZ8S9V', True),
        (109667145845879622871206540411193812284, '2JG4FVY7N8XS4GFVHPXGJZ8S9W', True),
        # Invalid ULID for bool format
        (True, None, False),
        (False, None, False),
    ],
)
def test_format_for_ulid(ulid: Any, result: Any, valid: bool):
    if valid:
        assert str(Something(ulid=ulid).ulid) == result
    else:
        with pytest.raises(ValidationError, match='format'):
            Something(ulid=ulid)


def test_property_for_ulid():
    ulid = Something(ulid='01BTGNYV6HRNK8K8VKZASZCFPE').ulid
    assert ulid.hex == '015ea15f6cd1c56689a373fab3f63ece'
    assert ulid == '01BTGNYV6HRNK8K8VKZASZCFPE'
    assert ulid.datetime == datetime(2017, 9, 20, 22, 18, 59, 153000, tzinfo=timezone.utc)
    assert ulid.timestamp == 1505945939.153


def test_json_schema():
    assert Something.model_json_schema(mode='validation') == {
        'properties': {
            'ulid': {
                'anyOf': [
                    {'type': 'integer'},
                    {'format': 'binary', 'type': 'string'},
                    {'type': 'string'},
                    {'format': 'uuid', 'type': 'string'},
                ],
                'title': 'Ulid',
            }
        },
        'required': ['ulid'],
        'title': 'Something',
        'type': 'object',
    }
    assert Something.model_json_schema(mode='serialization') == {
        'properties': {
            'ulid': {
                'anyOf': [
                    {'type': 'integer'},
                    {'format': 'binary', 'type': 'string'},
                    {'type': 'string'},
                    {'format': 'uuid', 'type': 'string'},
                ],
                'title': 'Ulid',
            }
        },
        'required': ['ulid'],
        'title': 'Something',
        'type': 'object',
    }
