from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest
from fastapi import FastAPI
from httpx import ASGITransport

from app.auth.tenant import get_current_restaurant, get_tenant_session
from app.routers.whatsapp_management import router


def _mock_restaurant(restaurant_id: str = "rest-123"):
    restaurant = MagicMock()
    restaurant.id = restaurant_id
    restaurant.name = "Test Restaurant"
    return restaurant


def _mock_account(restaurant_id: str = "rest-123", account_id: str = "wa-acc-1"):
    account = MagicMock()
    account.id = account_id
    account.restaurant_id = restaurant_id
    account.is_active = True
    account.phone_number_id = "12345"
    return account


def _mock_template(
    template_id: str = "tmpl-1",
    name: str = "greeting",
    category: str = "UTILITY",
    language: str = "en_US",
    status: str = "APPROVED",
):
    template = MagicMock()
    template.id = template_id
    template.name = name
    template.category = category
    template.language = language
    template.status = status
    template.components = None
    template.rejection_reason = None
    template.quality_score = None
    template.created_at = datetime(2025, 1, 1, tzinfo=UTC)
    template.updated_at = None
    return template


@pytest.fixture
def templates_app(mock_session):
    app = FastAPI()
    app.include_router(router, prefix="/api/v1")

    restaurant = _mock_restaurant()

    async def override_get_tenant_session():
        return mock_session

    async def override_get_current_restaurant():
        return restaurant

    app.dependency_overrides[get_tenant_session] = override_get_tenant_session
    app.dependency_overrides[get_current_restaurant] = override_get_current_restaurant
    yield app
    app.dependency_overrides.clear()


@pytest.mark.asyncio(loop_scope="session")
async def test_list_templates_success(templates_app, mock_session) -> None:
    account = _mock_account()
    template = _mock_template()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_templates = MagicMock()
    result_templates.scalars.return_value.all.return_value = [template]

    mock_session.execute = AsyncMock(side_effect=[result_account, result_templates])

    transport = ASGITransport(app=templates_app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/whatsapp/templates")

    assert response.status_code == 200
    assert response.json() == [
        {
            "id": "tmpl-1",
            "name": "greeting",
            "category": "UTILITY",
            "language": "en_US",
            "status": "APPROVED",
            "components": None,
            "rejection_reason": None,
            "quality_score": None,
            "created_at": "2025-01-01T00:00:00+00:00",
            "updated_at": None,
        }
    ]


@pytest.mark.asyncio(loop_scope="session")
async def test_list_templates_no_account(templates_app, mock_session) -> None:
    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = None

    mock_session.execute = AsyncMock(return_value=result_account)

    transport = ASGITransport(app=templates_app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/whatsapp/templates")

    assert response.status_code == 404
    assert response.json() == {"detail": "No active WhatsApp account found"}


@pytest.mark.asyncio(loop_scope="session")
async def test_create_template_success(templates_app, mock_session) -> None:
    account = _mock_account()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_existing = MagicMock()
    result_existing.scalar_one_or_none.return_value = None

    mock_session.execute = AsyncMock(side_effect=[result_account, result_existing])

    async def refresh_template(template) -> None:
        template.created_at = datetime(2025, 1, 1, tzinfo=UTC)
        template.updated_at = None

    mock_session.refresh.side_effect = refresh_template

    transport = ASGITransport(app=templates_app)
    with patch(
        "app.routers.whatsapp_management.commit_write",
        new_callable=AsyncMock,
    ) as mock_commit_write:
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/v1/whatsapp/templates",
                json={
                    "name": "  order_update  ",
                    "category": "utility",
                    "language": " en_US ",
                    "components": {"body": "Your order is ready"},
                },
            )

    assert response.status_code == 201
    body = response.json()
    assert body["name"] == "order_update"
    assert body["category"] == "UTILITY"
    assert body["language"] == "en_US"
    assert body["status"] == "PENDING"
    assert body["components"] == {"body": "Your order is ready"}
    assert body["created_at"] == "2025-01-01T00:00:00+00:00"
    assert body["updated_at"] is None

    added_template = mock_session.add.call_args.args[0]
    assert added_template.restaurant_id == "rest-123"
    assert added_template.whatsapp_account_id == "wa-acc-1"
    assert added_template.name == "order_update"
    assert added_template.category == "UTILITY"
    assert added_template.language == "en_US"
    assert added_template.status == "PENDING"
    assert added_template.components == {"body": "Your order is ready"}
    mock_commit_write.assert_awaited_once_with(mock_session)
    mock_session.refresh.assert_awaited_once_with(added_template)


@pytest.mark.asyncio(loop_scope="session")
async def test_create_template_invalid_category(templates_app, mock_session) -> None:
    account = _mock_account()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    mock_session.execute = AsyncMock(return_value=result_account)

    transport = ASGITransport(app=templates_app)
    with patch(
        "app.routers.whatsapp_management.commit_write",
        new_callable=AsyncMock,
    ) as mock_commit_write:
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/v1/whatsapp/templates",
                json={
                    "name": "order_update",
                    "category": "unknown",
                    "language": "en_US",
                },
            )

    assert response.status_code == 400
    assert response.json() == {"detail": "Invalid template category"}
    mock_commit_write.assert_not_awaited()
    mock_session.add.assert_not_called()


@pytest.mark.asyncio(loop_scope="session")
async def test_create_template_duplicate(templates_app, mock_session) -> None:
    account = _mock_account()
    existing_template = _mock_template(name="order_update", language="en_US")

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_existing = MagicMock()
    result_existing.scalar_one_or_none.return_value = existing_template

    mock_session.execute = AsyncMock(side_effect=[result_account, result_existing])

    transport = ASGITransport(app=templates_app)
    with patch(
        "app.routers.whatsapp_management.commit_write",
        new_callable=AsyncMock,
    ) as mock_commit_write:
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/v1/whatsapp/templates",
                json={
                    "name": "order_update",
                    "category": "UTILITY",
                    "language": "en_US",
                },
            )

    assert response.status_code == 409
    assert response.json() == {
        "detail": "A WhatsApp template with this name and language already exists"
    }
    mock_commit_write.assert_not_awaited()
    mock_session.add.assert_not_called()


@pytest.mark.asyncio(loop_scope="session")
async def test_get_template_success(templates_app, mock_session) -> None:
    account = _mock_account()
    template = _mock_template()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_template = MagicMock()
    result_template.scalar_one_or_none.return_value = template

    mock_session.execute = AsyncMock(side_effect=[result_account, result_template])

    transport = ASGITransport(app=templates_app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/whatsapp/templates/tmpl-1")

    assert response.status_code == 200
    assert response.json() == {
        "id": "tmpl-1",
        "name": "greeting",
        "category": "UTILITY",
        "language": "en_US",
        "status": "APPROVED",
        "components": None,
        "rejection_reason": None,
        "quality_score": None,
        "created_at": "2025-01-01T00:00:00+00:00",
        "updated_at": None,
    }


@pytest.mark.asyncio(loop_scope="session")
async def test_get_template_not_found(templates_app, mock_session) -> None:
    account = _mock_account()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_template = MagicMock()
    result_template.scalar_one_or_none.return_value = None

    mock_session.execute = AsyncMock(side_effect=[result_account, result_template])

    transport = ASGITransport(app=templates_app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/whatsapp/templates/missing-template")

    assert response.status_code == 404
    assert response.json() == {"detail": "WhatsApp template not found"}


@pytest.mark.asyncio(loop_scope="session")
async def test_delete_template_success(templates_app, mock_session) -> None:
    account = _mock_account()
    template = _mock_template()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_template = MagicMock()
    result_template.scalar_one_or_none.return_value = template

    mock_session.execute = AsyncMock(side_effect=[result_account, result_template])

    transport = ASGITransport(app=templates_app)
    with patch(
        "app.routers.whatsapp_management.commit_write",
        new_callable=AsyncMock,
    ) as mock_commit_write:
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.delete("/api/v1/whatsapp/templates/tmpl-1")

    assert response.status_code == 200
    assert response.json() == {"message": "WhatsApp template deleted successfully"}
    mock_session.delete.assert_awaited_once_with(template)
    mock_commit_write.assert_awaited_once_with(mock_session)


@pytest.mark.asyncio(loop_scope="session")
async def test_delete_template_not_found(templates_app, mock_session) -> None:
    account = _mock_account()

    result_account = MagicMock()
    result_account.scalar_one_or_none.return_value = account

    result_template = MagicMock()
    result_template.scalar_one_or_none.return_value = None

    mock_session.execute = AsyncMock(side_effect=[result_account, result_template])

    transport = ASGITransport(app=templates_app)
    with patch(
        "app.routers.whatsapp_management.commit_write",
        new_callable=AsyncMock,
    ) as mock_commit_write:
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.delete("/api/v1/whatsapp/templates/missing-template")

    assert response.status_code == 404
    assert response.json() == {"detail": "WhatsApp template not found"}
    mock_session.delete.assert_not_called()
    mock_commit_write.assert_not_awaited()
