from datetime import datetime
from zoneinfo import ZoneInfo

from backend.data.contracts import (
    DataSource,
    DataUsage,
    ObservationMetadata,
    ProviderResult,
    SnapshotState,
)
from backend.data.gateway import MarketDataUnavailable
from backend.data.repository import MarketRepository
from backend.database.connection import Database
from backend.database.migrations import MIGRATIONS, MigrationRunner
from backend.features.market.insights.auction import build_auction, build_watchlist_rows
from backend.features.market.insights.dragon import build_dragon_list
from backend.features.market.insights.popularity import build_popularity
from backend.features.market.insights.service import MarketInsightService
from backend.features.market.insights.themes import build_theme_detail, build_theme_library
from backend.features.review.repository import ReviewRepository

SHANGHAI = ZoneInfo("Asia/Shanghai")


def test_auction_keeps_market_cores_and_isolates_real_limit_price() -> None:
    directory = {
        f"000{index:03d}.SZ": {
            "identifier": f"000{index:03d}.SZ",
            "code": f"000{index:03d}",
            "name": "ST样本" if index == 1 else f"样本{index}",
            "sector": "机器人",
        }
        for index in range(1, 36)
    }
    prior_limits = [
        {
            "identifier": identifier,
            "code": stock["code"],
            "name": stock["name"],
            "sector": "机器人",
            "streak": 3,
            "amount": index * 10_000_000,
        }
        for index, (identifier, stock) in enumerate(directory.items(), start=1)
    ]
    rows = tuple(
        {
            "ts_code": identifier,
            "price": 11 if index == 1 else 10.5,
            "pre_close": 10,
            "amount": 5_000_000,
            "vol": 500_000,
            "turnover_rate": 0.2,
            "volume_ratio": 1.5,
        }
        for index, identifier in enumerate(directory, start=1)
    )
    result = build_auction(
        trade_date="2026-07-30",
        raw_rows=rows,
        price_limits=({"ts_code": "000001.SZ", "up_limit": 11},),
        directory=directory,
        prior_snapshot={
            "limits": prior_limits,
            "broken": [],
            "sectors": [{"name": "机器人", "count": 35}],
        },
        ths_hot=(),
        dc_hot=(),
        history=[
            {"trade_date": "2026-07-29", "amount_billion": 1.2, "stock_count": 35}
        ],
        dynamic=False,
    )

    assert result["summary"]["one_price_count"] == 1
    assert result["one_price_rows"][0]["code"] == "000001"
    assert result["one_price_rows"][0]["is_market_core"] is True
    assert len(result["focus_rows"]) == 34
    assert result["summary"]["amount_billion"] == result["amount_history"][-1]["amount_billion"]


def test_watchlist_rows_are_built_only_from_current_account_entries() -> None:
    market = [
        {
            "identifier": "000001.SZ",
            "code": "000001",
            "name": "平安银行",
            "sector": "银行",
            "change": 1.5,
            "amount_million": 10,
            "volume_ratio": 1.2,
        }
    ]
    rows = build_watchlist_rows(
        market,
        [],
        [],
        ({"identifier": "000001.SZ", "name": "平安银行", "sector": "银行"},),
    )

    assert [row["identifier"] for row in rows] == ["000001.SZ"]
    assert rows[0]["is_watchlist"] is True
    assert rows[0]["available"] is True


def test_watchlist_repository_isolates_accounts(tmp_path) -> None:
    database = Database(tmp_path / "watchlists.db")
    MigrationRunner(database).upgrade(MIGRATIONS)
    repository = ReviewRepository()
    with database.transaction() as connection:
        for user_id, username in ((1, "account-a"), (2, "account-b")):
            connection.execute(
                """
                INSERT INTO users (
                    id, username, username_key, password_hash, is_admin,
                    status, created_at, updated_at
                ) VALUES (?, ?, ?, 'hash', 0, 'active', '2026-07-30', '2026-07-30')
                """,
                (user_id, username, username),
            )
        connection.executemany(
            """
            INSERT INTO watchlist_entries (
                user_id, identifier, name, sector, created_at
            ) VALUES (?, ?, ?, ?, '2026-07-30')
            """,
            (
                (1, "000001.SZ", "平安银行", "银行"),
                (2, "000002.SZ", "万科A", "房地产"),
            ),
        )

    with database.read() as connection:
        first = repository.watchlist(connection, 1)
        second = repository.watchlist(connection, 2)

    assert [row["identifier"] for row in first] == ["000001.SZ"]
    assert [row["identifier"] for row in second] == ["000002.SZ"]


def test_popularity_preserves_single_source_without_false_consensus() -> None:
    ths = (
        {
            "data_type": "热股",
            "rank": 1,
            "ts_code": "000001.SZ",
            "ts_name": "平安银行",
            "pct_change": 2,
            "concept": '["银行"]',
        },
    )
    result = build_popularity("2026-07-30", ths, (), (), ())

    assert result["summary"] == {"ths_count": 1, "dc_count": 0, "dual_count": 0}
    assert result["combined"][0]["dual_source"] is False
    assert result["combined"][0]["concepts"] == ["银行"]


def test_theme_library_and_detail_keep_quote_and_member_empty_states_separate() -> None:
    library = build_theme_library(
        "2026-07-30",
        (
            {
                "ts_code": "885001.TI",
                "name": "机器人",
                "count": 2,
                "exchange": "A",
                "type": "N",
            },
        ),
        (),
        (),
    )
    assert library["summary"]["theme_count"] == 1
    assert library["summary"]["quoted_count"] == 0

    detail = build_theme_detail(
        "2026-07-30",
        library["items"][0],
        ({"con_code": "000001.SZ", "con_name": "平安银行"},),
        (),
    )
    assert detail["summary"]["member_count"] == 1
    assert detail["summary"]["quoted_count"] == 0
    assert detail["members"][0]["quoted"] is False


def test_dragon_list_distinguishes_missing_and_unclassified_seats() -> None:
    stocks = (
        {
            "ts_code": "000001.SZ",
            "name": "平安银行",
            "pct_change": 3.2,
            "reason": "日涨幅偏离值达7%",
        },
    )
    missing = build_dragon_list(
        trade_date="2026-07-30",
        official_rows=(),
        profile_rows=(),
        stock_rows=stocks,
        seat_rows=(),
        aliases={},
    )
    assert missing["status"] == "detail_missing"
    assert "1 只股票上榜" in missing["message"]

    seats = (
        {
            "ts_code": "000001.SZ",
            "exalter": "测试营业部",
            "buy": 20_000_000,
            "sell": 5_000_000,
            "net_buy": 15_000_000,
        },
    )
    pending = build_dragon_list(
        trade_date="2026-07-30",
        official_rows=(),
        profile_rows=(),
        stock_rows=stocks,
        seat_rows=seats,
        aliases={},
    )
    assert pending["status"] == "unclassified"
    assert pending["summary"]["unclassified_count"] == 1

    classified = build_dragon_list(
        trade_date="2026-07-30",
        official_rows=(),
        profile_rows=(),
        stock_rows=stocks,
        seat_rows=seats,
        aliases={"测试营业部": "测试游资"},
    )
    assert classified["status"] == "success"
    assert classified["traders"][0]["name"] == "测试游资"


class AuctionGateway:
    def __init__(self, dynamic_available: bool = True) -> None:
        self.dynamic_available = dynamic_available

    def trading_dates(self, through: str, limit: int = 2) -> tuple[str, ...]:
        dates = ("2026-07-30", "2026-07-29", "2026-07-28")
        return tuple(value for value in dates if value <= through)[:limit]

    def insight_inputs(
        self, kind: str, trade_date: str, previous: str = "", identifier: str = ""
    ) -> dict:
        assert kind == "auction"
        return {
            "auction": result(()),
            "price_limits": result(({"ts_code": "000001.SZ", "up_limit": 11},)),
            "ths_hot": result(()),
            "dc_hot": result(()),
        }

    def stock_directory(self) -> dict[str, dict]:
        return {
            "000001.SZ": {
                "identifier": "000001.SZ",
                "code": "000001",
                "name": "平安银行",
                "sector": "银行",
            }
        }

    def dynamic_auction(
        self, identifiers: tuple[str, ...], start_time: str, end_time: str
    ) -> ProviderResult:
        if not self.dynamic_available:
            raise MarketDataUnavailable("动态竞价暂不可用")
        assert identifiers == ("000001.SZ",)
        assert start_time.endswith("09:15:00")
        return result(
            (
                {
                    "thscode": "000001.SZ",
                    "time": end_time,
                    "latest": 10.5,
                    "preClose": 10,
                    "volume": 1_000_000,
                    "amount": 10_500_000,
                    "turnoverRatio": 0.2,
                    "volumeRatio": 1.5,
                },
            ),
            source=DataSource.IFIND,
            state=SnapshotState.REALTIME,
        )


def result(
    rows: tuple[dict, ...],
    *,
    source: DataSource = DataSource.TUSHARE,
    state: SnapshotState = SnapshotState.ARCHIVE,
) -> ProviderResult:
    return ProviderResult(
        rows,
        ObservationMetadata(
            source=source,
            observed_at=datetime(2026, 7, 30, 9, 20, tzinfo=SHANGHAI),
            unit="mixed",
            adjustment="not_applicable",
            freshness_seconds=0,
            coverage=1,
            state=state,
            usage=DataUsage.CALCULATION,
        ),
    )


def insight_service(tmp_path, dynamic_available: bool = True) -> MarketInsightService:
    database = Database(tmp_path / "insights.db")
    MigrationRunner(database).upgrade(MIGRATIONS)
    repository = MarketRepository()
    prior = {
        "trade_date": "2026-07-29",
        "limits": [
            {
                "identifier": "000001.SZ",
                "code": "000001",
                "name": "平安银行",
                "sector": "银行",
                "streak": 1,
                "amount": 200_000_000,
            }
        ],
        "broken": [],
        "sectors": [{"name": "银行", "count": 1}],
    }
    with database.transaction() as connection:
        repository.save_summary(
            connection,
            trade_date="2026-07-29",
            observed_at="2026-07-29T15:00:00+08:00",
            state="final",
            source="tushare",
            coverage=1,
            payload=prior,
        )
    return MarketInsightService(
        database,
        repository,
        ReviewRepository(),
        AuctionGateway(dynamic_available),  # type: ignore[arg-type]
    )


def test_auction_lifecycle_uses_live_snapshot_then_archives_925_result(tmp_path) -> None:
    service = insight_service(tmp_path)
    observing = service.auction(
        "2026-07-30", user_id=1, now=datetime(2026, 7, 30, 9, 20, tzinfo=SHANGHAI)
    )
    assert observing["phase"] == "observing"
    assert observing["trade_date"] == "2026-07-30"
    assert observing["dynamic"] is True
    assert observing["current_available"] is True

    selection = service.auction(
        "2026-07-30", user_id=1, now=datetime(2026, 7, 30, 9, 26, tzinfo=SHANGHAI)
    )
    assert selection["phase"] == "selection"
    assert selection["state"] == "final"
    with service._database.read() as connection:
        stored = service._repository.insight_snapshot(
            connection, "auction", "2026-07-30"
        )
    assert stored is not None


def test_observing_without_dynamic_data_never_disguises_previous_archive(tmp_path) -> None:
    service = insight_service(tmp_path, dynamic_available=False)
    previous = {
        "trade_date": "2026-07-29",
        "observed_at": "2026-07-29T09:25:00+08:00",
        "state": "archive",
        "summary": {"stock_count": 1, "amount_billion": 0.1},
        "focus_rows": [],
        "one_price_rows": [],
        "rows": [],
        "amount_history": [],
    }
    with service._database.transaction() as connection:
        service._repository.save_insight_snapshot(
            connection,
            kind="auction",
            trade_date="2026-07-29",
            entity_key="",
            observed_at=previous["observed_at"],
            state="archive",
            source="tushare",
            coverage=1,
            payload=previous,
        )
    current = service.auction(
        "2026-07-30", user_id=1, now=datetime(2026, 7, 30, 9, 20, tzinfo=SHANGHAI)
    )
    assert current["trade_date"] == "2026-07-29"
    assert current["carried_forward"] is True
    assert current["current_available"] is False
    assert "动态竞价暂不可用" in current["message"]
