from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock
from uuid import UUID, uuid4

import pytest
from fastapi import HTTPException

from core.auth.models import CurrentUser
from models.schedule_items import (
    ScheduleItem,
    ScheduleItemSourceType,
    ScheduleItemStatus,
)
from v1.schedule_items.schemas import (
    ScheduleItemCreateRequest,
    ScheduleItemUpdateRequest,
)
from v1.schedule_items.service import ScheduleItemService


def _create_mock_schedule_item(
    item_id: UUID = uuid4(),
    owner_id: UUID = UUID("00000000-0000-0000-0000-000000000001"),
    title: str = "Test Event",
) -> ScheduleItem:
    item = MagicMock(spec=ScheduleItem)
    item.id = item_id
    item.owner_id = owner_id
    item.title = title
    item.description = None
    item.start_at = datetime(2026, 2, 28, 16, 0, 0, tzinfo=timezone.utc)
    item.end_at = datetime(2026, 2, 28, 17, 0, 0, tzinfo=timezone.utc)
    item.timezone = "UTC"
    item.extra_metadata = {}
    item.source_type = ScheduleItemSourceType.MANUAL
    item.status = ScheduleItemStatus.ACTIVE
    item.created_at = datetime(2026, 2, 27, 10, 0, 0, tzinfo=timezone.utc)
    item.updated_at = datetime(2026, 2, 27, 10, 0, 0, tzinfo=timezone.utc)
    return item


class FakeRepo:
    def __init__(self, item: ScheduleItem | None) -> None:
        self._item = item

    async def get_by_item_id(
        self, item_id: UUID, owner_id: UUID
    ) -> ScheduleItem | None:
        if self._item and item_id == self._item.id:
            return self._item
        return None

    async def create(self, data: dict) -> ScheduleItem:
        return _create_mock_schedule_item(
            owner_id=data["owner_id"],
            title=data["title"],
        )

    async def update_by_item_id(
        self, item_id: UUID, owner_id: UUID, data: dict
    ) -> ScheduleItem | None:
        if not self._item or item_id != self._item.id:
            return None
        if "title" in data:
            self._item.title = data["title"]
        return self._item

    async def delete_by_item_id(
        self, item_id: UUID, owner_id: UUID
    ) -> ScheduleItem | None:
        if not self._item or item_id != self._item.id:
            return None
        return self._item

    async def list_by_date_range(
        self, owner_id: UUID, start_at: datetime, end_at: datetime
    ) -> list[ScheduleItem]:
        return [self._item] if self._item else []


@pytest.fixture
def mock_session() -> AsyncMock:
    session = AsyncMock()
    session.commit = AsyncMock()
    session.rollback = AsyncMock()
    return session


@pytest.mark.asyncio
async def test_create_success(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    request = ScheduleItemCreateRequest(
        title="Test Event",
        start_at=datetime(2026, 2, 28, 16, 0, 0, tzinfo=timezone.utc),
    )
    service = ScheduleItemService(
        repository=FakeRepo(None),
        session=mock_session,
        current_user=CurrentUser(id=user_id),
    )

    result = await service.create(request)

    assert result.title == "Test Event"
    mock_session.commit.assert_awaited_once()


@pytest.mark.asyncio
async def test_create_invalid_end_at(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    request = ScheduleItemCreateRequest(
        title="Test Event",
        start_at=datetime(2026, 2, 28, 17, 0, 0, tzinfo=timezone.utc),
        end_at=datetime(2026, 2, 28, 16, 0, 0, tzinfo=timezone.utc),
    )
    service = ScheduleItemService(
        repository=FakeRepo(None),
        session=mock_session,
        current_user=CurrentUser(id=user_id),
    )

    with pytest.raises(HTTPException) as exc_info:
        await service.create(request)

    assert exc_info.value.status_code == 400


@pytest.mark.asyncio
async def test_get_by_id_success(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    item = _create_mock_schedule_item()
    service = ScheduleItemService(
        repository=FakeRepo(item),
        session=mock_session,
        current_user=CurrentUser(id=user_id),
    )

    result = await service.get_by_id(item.id)

    assert result.id == item.id


@pytest.mark.asyncio
async def test_get_by_id_not_found(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    service = ScheduleItemService(
        repository=FakeRepo(None),
        session=mock_session,
        current_user=CurrentUser(id=user_id),
    )

    with pytest.raises(HTTPException) as exc_info:
        await service.get_by_id(uuid4())

    assert exc_info.value.status_code == 404


@pytest.mark.asyncio
async def test_update_success(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    item = _create_mock_schedule_item()
    service = ScheduleItemService(
        repository=FakeRepo(item),
        session=mock_session,
        current_user=CurrentUser(id=user_id),
    )

    result = await service.update(item.id, ScheduleItemUpdateRequest(title="Updated"))

    assert result.title == "Updated"


@pytest.mark.asyncio
async def test_delete_success(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    item = _create_mock_schedule_item()
    service = ScheduleItemService(
        repository=FakeRepo(item),
        session=mock_session,
        current_user=CurrentUser(id=user_id),
    )

    await service.delete(item.id)

    mock_session.commit.assert_awaited_once()
