import hashlib
import uuid
from unittest.mock import AsyncMock, patch, MagicMock

import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool

from app.api.deps import get_current_user, get_db
from app.database import Base
from app.main import app
from app.models.brand import Brand
from app.models.user import User
from app.services.auth import hash_password
from app.services.diagnosis.geo_diagnosis import GEODiagnosisInput, GEODiagnosisResult


def _make_user(
    user_id: str | None = None,
    email: str = "test@example.com",
    plan: str = "free",
) -> User:
    uid = user_id or str(uuid.uuid4())
    user = User(
        id=uid,
        email=email,
        password=hash_password("Test@123456"),
        firstName="Test",
        lastName="User",
        isActive=True,
        emailVerified=True,
    )
    user.plan = plan
    user.max_queries = 50 if plan != "free" else 5
    return user


def _to_uuid(value: str | uuid.UUID) -> uuid.UUID:
    if isinstance(value, uuid.UUID):
        return value
    return uuid.UUID(str(value))


@pytest_asyncio.fixture
async def async_engine():
    engine = create_async_engine(
        "sqlite+aiosqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield engine
    await engine.dispose()


@pytest_asyncio.fixture
async def async_session(async_engine):
    maker = async_sessionmaker(
        async_engine,
        class_=AsyncSession,
        expire_on_commit=False,
        autoflush=False,
        autocommit=False,
    )
    async with maker() as session:
        yield session


@pytest_asyncio.fixture
async def test_user(async_session):
    user = _make_user(plan="free")
    async_session.add(user)
    await async_session.commit()
    await async_session.refresh(user)
    return user


@pytest_asyncio.fixture
async def test_brand(async_session, test_user):
    brand = Brand(
        id=uuid.uuid4(),
        user_id=_to_uuid(test_user.id),
        name="TestBrand",
        aliases=["TB", "Test Brand"],
        website="https://testbrand.com",
        industry="technology",
        platforms=["wenxin", "kimi"],
        frequency="weekly",
        status="active",
    )
    async_session.add(brand)
    await async_session.commit()
    await async_session.refresh(brand)
    return brand


@pytest_asyncio.fixture
async def public_client(async_session):
    async def override_get_db():
        yield async_session

    app.dependency_overrides[get_db] = override_get_db

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client

    app.dependency_overrides.clear()


class TestHealthScorePublicContract:
    @pytest.mark.asyncio
    async def test_returns_200_with_brand(self, public_client, test_brand):
        mock_cache = AsyncMock()
        mock_cache.get_json = AsyncMock(return_value=None)
        mock_cache.set_json = AsyncMock()

        mock_result = MagicMock()
        mock_result.to_dict.return_value = {
            "overall_score": 45.0,
            "health_level": "pass",
            "health_level_label": "及格",
            "dimensions": [
                {"name": "内容可提取性", "score": 12.0, "max_score": 20.0, "percentage": 60.0, "status": "pass"},
                {"name": "E-E-A-T信号", "score": 10.0, "max_score": 20.0, "percentage": 50.0, "status": "pass"},
                {"name": "引用就绪度", "score": 8.0, "max_score": 15.0, "percentage": 53.3, "status": "pass"},
                {"name": "实体清晰度", "score": 8.0, "max_score": 15.0, "percentage": 53.3, "status": "pass"},
                {"name": "Schema标记", "score": 5.0, "max_score": 15.0, "percentage": 33.3, "status": "fail"},
                {"name": "主题权威", "score": 6.0, "max_score": 15.0, "percentage": 40.0, "status": "pass"},
            ],
            "recommendations": [
                {"priority": "P0", "dimension": "内容可提取性", "title": "添加结构化数据", "description": "建议添加Schema标记"},
            ],
        }

        with patch("app.api.health_score.get_cache_service", return_value=mock_cache), \
             patch("app.api.health_score.DataCollectorService") as MockCollector, \
             patch("app.api.health_score.GEODiagnosisService") as MockDiagService:
            mock_collector = MockCollector.return_value
            mock_collection = MagicMock()
            mock_collection.diagnosis_input = GEODiagnosisInput()
            mock_collector.collect = AsyncMock(return_value=mock_collection)

            mock_diag = MockDiagService.return_value
            mock_diag.diagnose = MagicMock(return_value=mock_result)

            response = await public_client.get(
                "/api/v1/public/health-score",
                params={"brand": "TestBrand"},
            )

        assert response.status_code == 200
        data = response.json()
        assert data["brand_name"] == "TestBrand"
        assert data["overall_score"] > 0
        assert data["health_level"] in ("excellent", "good", "pass", "danger")
        assert data["is_full_report"] is False
        assert len(data["dimensions"]) == 3
        dim_names = {d["name"] for d in data["dimensions"]}
        assert "内容可提取性" in dim_names
        assert "E-E-A-T信号" in dim_names
        assert "引用就绪度" in dim_names

    @pytest.mark.asyncio
    async def test_returns_cached_result(self, public_client):
        cached_data = {
            "brand_name": "CachedBrand",
            "overall_score": 55.0,
            "health_level": "pass",
            "health_level_label": "及格",
            "dimensions": [
                {"name": "内容可提取性", "score": 15.0, "max_score": 20.0, "percentage": 75.0, "status": "good"},
                {"name": "E-E-A-T信号", "score": 12.0, "max_score": 20.0, "percentage": 60.0, "status": "pass"},
                {"name": "引用就绪度", "score": 10.0, "max_score": 15.0, "percentage": 66.7, "status": "pass"},
            ],
            "recommendations": [],
            "is_full_report": False,
            "cached": False,
        }

        mock_cache = AsyncMock()
        mock_cache.get_json = AsyncMock(return_value=cached_data)

        with patch("app.api.health_score.get_cache_service", return_value=mock_cache):
            response = await public_client.get(
                "/api/v1/public/health-score",
                params={"brand": "CachedBrand"},
            )

        assert response.status_code == 200
        data = response.json()
        assert data["cached"] is True
        assert data["brand_name"] == "CachedBrand"

    @pytest.mark.asyncio
    async def test_missing_brand_param_returns_422(self, public_client):
        response = await public_client.get("/api/v1/public/health-score")
        assert response.status_code == 422

    @pytest.mark.asyncio
    async def test_collection_failure_returns_default(self, public_client):
        mock_cache = AsyncMock()
        mock_cache.get_json = AsyncMock(return_value=None)
        mock_cache.set_json = AsyncMock()

        with patch("app.api.health_score.get_cache_service", return_value=mock_cache), \
             patch("app.api.health_score.DataCollectorService") as MockCollector:
            mock_collector = MockCollector.return_value
            mock_collector.collect = AsyncMock(side_effect=Exception("采集失败"))

            response = await public_client.get(
                "/api/v1/public/health-score",
                params={"brand": "UnknownBrand"},
            )

        assert response.status_code == 200
        data = response.json()
        assert data["brand_name"] == "UnknownBrand"
        assert data["overall_score"] == 0.0
        assert data["health_level"] == "danger"

    @pytest.mark.asyncio
    async def test_competitors_param_accepted(self, public_client):
        mock_cache = AsyncMock()
        mock_cache.get_json = AsyncMock(return_value=None)
        mock_cache.set_json = AsyncMock()

        mock_result = MagicMock()
        mock_result.to_dict.return_value = {
            "overall_score": 30.0,
            "health_level": "pass",
            "health_level_label": "及格",
            "dimensions": [
                {"name": "内容可提取性", "score": 10.0, "max_score": 20.0, "percentage": 50.0, "status": "pass"},
                {"name": "E-E-A-T信号", "score": 8.0, "max_score": 20.0, "percentage": 40.0, "status": "fail"},
                {"name": "引用就绪度", "score": 5.0, "max_score": 15.0, "percentage": 33.3, "status": "fail"},
                {"name": "实体清晰度", "score": 5.0, "max_score": 15.0, "percentage": 33.3, "status": "fail"},
                {"name": "Schema标记", "score": 3.0, "max_score": 15.0, "percentage": 20.0, "status": "fail"},
                {"name": "主题权威", "score": 4.0, "max_score": 15.0, "percentage": 26.7, "status": "fail"},
            ],
            "recommendations": [],
        }

        with patch("app.api.health_score.get_cache_service", return_value=mock_cache), \
             patch("app.api.health_score.DataCollectorService") as MockCollector, \
             patch("app.api.health_score.GEODiagnosisService") as MockDiagService:
            mock_collector = MockCollector.return_value
            mock_collection = MagicMock()
            mock_collection.diagnosis_input = GEODiagnosisInput()
            mock_collector.collect = AsyncMock(return_value=mock_collection)

            mock_diag = MockDiagService.return_value
            mock_diag.diagnose = MagicMock(return_value=mock_result)

            response = await public_client.get(
                "/api/v1/public/health-score",
                params={"brand": "SomeBrand", "competitors": "CompA,CompB,CompC"},
            )

        assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_only_p0_recommendations_returned(self, public_client):
        mock_cache = AsyncMock()
        mock_cache.get_json = AsyncMock(return_value=None)
        mock_cache.set_json = AsyncMock()

        mock_result = MagicMock()
        mock_result.to_dict.return_value = {
            "overall_score": 30.0,
            "health_level": "pass",
            "health_level_label": "及格",
            "dimensions": [
                {"name": "内容可提取性", "score": 10.0, "max_score": 20.0, "percentage": 50.0, "status": "pass"},
                {"name": "E-E-A-T信号", "score": 8.0, "max_score": 20.0, "percentage": 40.0, "status": "fail"},
                {"name": "引用就绪度", "score": 5.0, "max_score": 15.0, "percentage": 33.3, "status": "fail"},
            ],
            "recommendations": [
                {"priority": "P0", "dimension": "内容可提取性", "title": "P0建议", "description": "紧急"},
                {"priority": "P1", "dimension": "E-E-A-T信号", "title": "P1建议", "description": "重要"},
                {"priority": "P2", "dimension": "引用就绪度", "title": "P2建议", "description": "一般"},
            ],
        }

        with patch("app.api.health_score.get_cache_service", return_value=mock_cache), \
             patch("app.api.health_score.DataCollectorService") as MockCollector, \
             patch("app.api.health_score.GEODiagnosisService") as MockDiagService:
            mock_collector = MockCollector.return_value
            mock_collection = MagicMock()
            mock_collection.diagnosis_input = GEODiagnosisInput()
            mock_collector.collect = AsyncMock(return_value=mock_collection)

            mock_diag = MockDiagService.return_value
            mock_diag.diagnose = MagicMock(return_value=mock_result)

            response = await public_client.get(
                "/api/v1/public/health-score",
                params={"brand": "TestBrand"},
            )

        assert response.status_code == 200
        data = response.json()
        assert len(data["recommendations"]) == 1
        assert data["recommendations"][0]["priority"] == "P0"

    @pytest.mark.asyncio
    async def test_no_auth_required(self, public_client):
        mock_cache = AsyncMock()
        mock_cache.get_json = AsyncMock(return_value={
            "brand_name": "Test",
            "overall_score": 50.0,
            "health_level": "pass",
            "health_level_label": "及格",
            "dimensions": [],
            "recommendations": [],
            "is_full_report": False,
            "cached": False,
        })

        with patch("app.api.health_score.get_cache_service", return_value=mock_cache):
            response = await public_client.get(
                "/api/v1/public/health-score",
                params={"brand": "Test"},
            )

        assert response.status_code == 200
