from __future__ import annotations

from pathlib import Path

import pytest
from sqlalchemy import Column, String, Table, func, select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

from core.db.base import Base
from core.config.initial import init_data
from models.llm import Llm
from models.llm_factory import LlmFactory


def test_llm_catalog_file_exists_and_has_required_fields() -> None:
    catalog_path = (
        Path(__file__).resolve().parents[3]
        / "src"
        / "core"
        / "config"
        / "static"
        / "database"
        / "llm_catalog.yaml"
    )

    catalog = init_data.load_llm_catalog(catalog_path)

    assert len(catalog["factories"]) == 6
    assert len(catalog["llms"]) == 2
    assert set(catalog["factories"][0].keys()) == {"name", "request_url", "avatar"}
    assert set(catalog["llms"][0].keys()) == {"model_code", "factory_name"}


def test_load_llm_catalog_raises_on_invalid_structure(tmp_path: Path) -> None:
    catalog_path = tmp_path / "llm_catalog.yaml"
    catalog_path.write_text(
        """
factories:
  - name: qwen
llms:
  - model_code: qwen3.5-flash
""".strip(),
        encoding="utf-8",
    )

    with pytest.raises(ValueError):
        init_data.load_llm_catalog(catalog_path)


@pytest.mark.asyncio
async def test_initialize_data_is_idempotent(monkeypatch: pytest.MonkeyPatch) -> None:
    users_table = Table(
        "users",
        Base.metadata,
        Column("id", String, primary_key=True),
        schema="auth",
        extend_existing=True,
    )
    engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
    session_maker = async_sessionmaker(
        bind=engine, class_=AsyncSession, expire_on_commit=False
    )

    async with engine.begin() as conn:
        await conn.exec_driver_sql("ATTACH DATABASE ':memory:' AS auth")
        await conn.run_sync(Base.metadata.create_all)

    monkeypatch.setattr(init_data, "AsyncSessionLocal", session_maker)

    first = await init_data.initialize_data()
    second = await init_data.initialize_data()

    assert first is True
    assert second is True

    async with session_maker() as session:
        factory_count = await session.scalar(
            select(func.count()).select_from(LlmFactory)
        )
        llm_count = await session.scalar(select(func.count()).select_from(Llm))

    assert factory_count == 6
    assert llm_count == 2

    Base.metadata.remove(users_table)
    await engine.dispose()


@pytest.mark.asyncio
async def test_initialize_data_rolls_back_on_invalid_factory_mapping(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    users_table = Table(
        "users",
        Base.metadata,
        Column("id", String, primary_key=True),
        schema="auth",
        extend_existing=True,
    )
    engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
    session_maker = async_sessionmaker(
        bind=engine, class_=AsyncSession, expire_on_commit=False
    )

    async with engine.begin() as conn:
        await conn.exec_driver_sql("ATTACH DATABASE ':memory:' AS auth")
        await conn.run_sync(Base.metadata.create_all)

    monkeypatch.setattr(init_data, "AsyncSessionLocal", session_maker)
    monkeypatch.setattr(
        init_data,
        "load_llm_catalog",
        lambda *_: {
            "factories": [
                {
                    "name": "qwen",
                    "request_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
                    "avatar": "https://cdn.example.com/qwen.png",
                }
            ],
            "llms": [
                {
                    "model_code": "qwen3.5-flash",
                    "factory_id": "missing_factory",
                }
            ],
        },
    )

    with pytest.raises(RuntimeError):
        await init_data.initialize_data()

    async with session_maker() as session:
        factory_count = await session.scalar(
            select(func.count()).select_from(LlmFactory)
        )
        llm_count = await session.scalar(select(func.count()).select_from(Llm))

    assert factory_count == 0
    assert llm_count == 0

    Base.metadata.remove(users_table)
    await engine.dispose()
