from __future__ import annotations

import tempfile
import unittest
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import patch

from chart_data_provider import EastmoneyChartClient, MarketChartClient
from database import ReviewDatabase
from market_insights import MarketInsightsService
from server import DashboardService


class FakeIfind:
    configured = True

    def history(self, codes, indicators, start_date, end_date, cache_ttl=0):
        return [
            {
                "time": "2026-07-27",
                "thscode": "000001.SZ",
                "open": 10,
                "high": 10.5,
                "low": 9.8,
                "close": 10.2,
                "volume": 100,
                "amount": 1_000_000,
            },
            {
                "time": "2026-07-28",
                "thscode": "000001.SZ",
                "open": 10.2,
                "high": 10.8,
                "low": 10.1,
                "close": 10.5,
                "volume": 120,
                "amount": 1_200_000,
            },
        ]

    def real_time(self, codes, indicators, cache_ttl=0):
        return []


class FakeIfindStalePreopen(FakeIfind):
    def history(self, codes, indicators, start_date, end_date, cache_ttl=0):
        return [
            *super().history(codes, indicators, start_date, end_date, cache_ttl),
            {
                "time": "2026-07-29",
                "thscode": "000001.SZ",
                "open": 10.5,
                "high": 10.5,
                "low": 10.5,
                "close": 10.5,
                "volume": 0,
                "amount": 0,
            },
        ]

    def real_time(self, codes, indicators, cache_ttl=0):
        return [
            {
                "time": "2026-07-28 15:00:00",
                "open": 10.2,
                "high": 10.8,
                "low": 10.1,
                "latest": 10.5,
                "preClose": 10.2,
                "volume": 120,
                "amount": 1_200_000,
            }
        ]


class FixedPreopenDatetime(datetime):
    fixed_now = datetime(2026, 7, 29, 8, 45, tzinfo=timezone(timedelta(hours=8)))

    @classmethod
    def now(cls, tz=None):
        return cls.fixed_now


class FakeIfindSnapshots:
    configured = True

    def __init__(self):
        self.calls = []

    def snapshots(self, codes, indicators, start_time, end_time, cache_ttl=0):
        self.calls.append(
            {
                "codes": codes,
                "indicators": indicators,
                "start_time": start_time,
                "end_time": end_time,
                "cache_ttl": cache_ttl,
            }
        )
        return [
            {
                "time": "2026-07-28 09:21:00",
                "thscode": "000001.SZ",
                "latest": 10.5,
                "preClose": 10,
                "volume": 2000,
                "amount": 21000,
                "bidSize1": 1200,
                "askSize1": 800,
            }
        ]


class FakeTushare:
    pass


class IfindFeatureTests(unittest.TestCase):
    def test_wencai_saved_queries_are_isolated_by_user(self):
        with tempfile.TemporaryDirectory() as temporary:
            database = ReviewDatabase(Path(temporary) / "review.db")
            first = database.create_user("first-user", "salt", "hash")
            second = database.create_user("second-user", "salt", "hash")
            database.save_wencai_query(first["id"], "高质量", "ROE大于15%", "stock")

            self.assertEqual(len(database.list_wencai_saved_queries(first["id"])), 1)
            self.assertEqual(database.list_wencai_saved_queries(second["id"]), [])

    def test_ifind_daily_chart_normalizes_change(self):
        client = MarketChartClient(FakeIfind(), EastmoneyChartClient())
        rows = client.stock_daily("000001", "20260728")
        self.assertEqual(rows[-1]["trade_date"], "2026-07-28")
        self.assertAlmostEqual(rows[-1]["change"], 2.9412, places=4)

    def test_ifind_daily_chart_keeps_last_traded_bar_before_market_open(self):
        client = MarketChartClient(FakeIfindStalePreopen(), EastmoneyChartClient())
        with patch("chart_data_provider.datetime", FixedPreopenDatetime):
            rows = client.stock_daily("000001", "20260729")

        self.assertEqual(rows[-1]["trade_date"], "2026-07-28")
        self.assertFalse(rows[-1].get("realtime", False))

    def test_event_enrichment_keeps_blank_broken_reason_blank(self):
        dashboard = {"broken": [{"code": "000001", "reason": "原原因"}]}
        DashboardService._merge_ifind_event_enrichment(
            dashboard,
            {
                "broken": {
                    "000001": {
                        "reason": "",
                        "first_time": "09:42:00",
                        "last_time": "",
                        "open_times": 3,
                    }
                }
            },
        )
        self.assertEqual(dashboard["broken"][0]["reason"], "原原因")
        self.assertEqual(dashboard["broken"][0]["open_times"], 3)

    def test_dynamic_auction_uses_ifind_snapshot_window_and_normalizes_rows(self):
        with tempfile.TemporaryDirectory() as temporary:
            database = ReviewDatabase(Path(temporary) / "review.db")
            database.upsert_stock_master(
                [
                    {
                        "ts_code": "000001.SZ",
                        "name": "Ping An Bank",
                        "industry": "Bank",
                        "market": "MainBoard",
                        "list_date": "19910403",
                    }
                ]
            )
            ifind = FakeIfindSnapshots()
            service = MarketInsightsService(
                database,
                FakeTushare(),
                now_provider=lambda: datetime(
                    2026, 7, 28, 9, 22, tzinfo=timezone(timedelta(hours=8))
                ),
                ifind=ifind,
            )
            service._auction_candidates = lambda rows, baseline: (
                [{"ts_code": "000001.SZ"}],
                {},
                [],
            )

            rows = service._dynamic_auction_rows("20260728", "20260727", 0)

            self.assertEqual(ifind.calls[0]["start_time"], "2026-07-28 09:15:00")
            self.assertEqual(ifind.calls[0]["end_time"], "2026-07-28 09:22:00")
            self.assertEqual(rows[0]["ts_code"], "000001.SZ")
            self.assertEqual(rows[0]["price"], 10.5)
            self.assertEqual(rows[0]["snapshot_time"], "2026-07-28 09:21:00")
            self.assertTrue(rows[0]["dynamic"])


if __name__ == "__main__":
    unittest.main()
