from __future__ import annotations

import copy
import threading
import unittest
from datetime import date, datetime, timedelta, timezone
from pathlib import Path

from backend.features.market.service import MarketServiceMixin
from backend.jobs.refresh import (
    dashboard_has_usable_data,
    official_catchup_due,
    verified_dashboard_result,
)
from backend.data.providers.tushare_transport import TushareError


class AdminRefreshStatusTests(unittest.TestCase):
    def test_carried_snapshot_is_usable_not_failed_job(self):
        result = verified_dashboard_result(
            {
                "meta": {
                    "trade_date": "2026-09-01",
                    "requested_date": "2026-09-02",
                    "carried_forward": True,
                    "notice": "今日数据正在准备，当前展示 9 月 1 日",
                    "data_status": "preparing",
                },
                "overview": {"limit_up_count": 12},
            }
        )

        self.assertNotEqual(result.get("status"), "failed")
        self.assertEqual(result["meta"]["data_status"], "preparing")
        self.assertTrue(dashboard_has_usable_data(result))

    def test_derived_limit_snapshot_is_usable_not_failed_job(self):
        dashboard = {
            "meta": {
                "trade_date": "2026-09-02",
                "limit_data_source": "derived",
                "notice": "涨跌停高级接口当日数据尚未更新，已使用日线数据推算。",
                "data_status": "partial",
            },
            "overview": {"limit_up_count": 8},
        }

        self.assertIs(verified_dashboard_result(dashboard), dashboard)

    def test_current_snapshot_is_reported_as_successful_job(self):
        dashboard = {"meta": {"trade_date": "2026-08-28", "carried_forward": False}}

        self.assertIs(verified_dashboard_result(dashboard), dashboard)

    def test_empty_payload_is_still_failed(self):
        result = verified_dashboard_result({"meta": {}, "overview": {}})
        self.assertEqual(result["status"], "failed")


class FakeSyncDatabase:
    def __init__(self, latest=None):
        self.latest = latest
        self.saved = []
        self.finished = []

    def start_sync(self, *_args, **_kwargs):
        return 1

    def save_snapshot(self, trade_date, source, payload):
        self.saved.append((trade_date, source, copy.deepcopy(payload)))

    def save_data_snapshot(self, *_args, **_kwargs):
        return None

    def finish_sync(self, *args, **kwargs):
        self.finished.append((args, kwargs))

    def get_latest_real_snapshot(self, *_args, **_kwargs):
        return copy.deepcopy(self.latest)

    def get_snapshot(self, *_args, **_kwargs):
        return None

    def get_data_snapshot(self, *_args, **_kwargs):
        return None

    def reason_overrides(self, *_args, **_kwargs):
        return {}


class FakeDerivedClient:
    def dashboard(self, trade_date: str):
        return {
            "meta": {
                "trade_date": f"{trade_date[:4]}-{trade_date[4:6]}-{trade_date[6:8]}",
                "limit_data_source": "derived",
                "notice": "涨跌停高级接口当日数据尚未更新，已使用日线数据推算。",
                "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
            },
            "overview": {"limit_up_count": 3},
            "limits": [{"code": "000001"}],
            "broken": [],
            "down_limits": [],
            "yesterday_limits": [],
        }


class FakeMissingDailyClient:
    def dashboard(self, trade_date: str):
        raise TushareError(f"No daily data returned for {trade_date}")


class SyncHarness(MarketServiceMixin):
    def __init__(self, client, latest=None):
        self.configured = True
        self.sync_lock = threading.Lock()
        self.database = FakeSyncDatabase(latest)
        self._client = client
        self.current_user_id = 1

    def _tushare_client(self):
        return self._client

    def _enrich_dashboard_sentiment(self, dashboard, _trade_date):
        return dashboard

    def _apply_reason_overrides(self, dashboard):
        return dashboard


class DashboardFreshnessTests(unittest.TestCase):
    def test_derived_limits_are_kept_as_partial_success(self):
        today = date.today().strftime("%Y%m%d")
        harness = SyncHarness(FakeDerivedClient())
        payload = harness.sync_dashboard(today)
        meta = payload["meta"]

        self.assertEqual(meta["limit_data_source"], "derived")
        self.assertEqual(meta["data_status"], "partial")
        self.assertFalse(meta.get("carried_forward"))
        self.assertIn("日线数据推算", meta["display_notice"])
        self.assertEqual(harness.database.finished[0][0][1], "success")
        self.assertEqual(verified_dashboard_result(payload), payload)

    def test_missing_official_data_keeps_previous_day_with_preparing_notice(self):
        today = date.today()
        previous = (today - timedelta(days=1)).strftime("%Y-%m-%d")
        latest = {
            "meta": {"trade_date": previous, "source": "tushare"},
            "overview": {"limit_up_count": 20},
        }
        harness = SyncHarness(FakeMissingDailyClient(), latest)
        payload = harness.sync_dashboard(today.strftime("%Y%m%d"))
        meta = payload["meta"]

        self.assertTrue(meta["carried_forward"])
        self.assertEqual(meta["data_status"], "preparing")
        self.assertIn("今日数据正在准备，当前展示", meta["display_notice"])
        self.assertIn("月", meta["display_notice"])
        self.assertNotIn("No daily data", meta["display_notice"])
        self.assertNotEqual(verified_dashboard_result(payload).get("status"), "failed")

    def test_weekend_carry_is_not_labeled_as_preparing(self):
        snapshot = {
            "meta": {"trade_date": "2026-07-24", "source": "tushare", "updated_at": "2026-07-24T15:00:00+08:00"},
            "overview": {"limit_up_count": 1},
        }
        harness = SyncHarness(FakeMissingDailyClient())
        carried = harness._carry_dashboard(snapshot, "20260725", "非交易日沿用最近交易日收盘行情")
        self.assertEqual(carried["meta"]["data_status"], "carried")
        self.assertIn("非交易日", carried["meta"]["display_notice"])

    def test_stale_derived_snapshot_is_retried(self):
        today = date.today().strftime("%Y%m%d")
        old = datetime.now(timezone.utc) - timedelta(minutes=5)
        snapshot = {
            "meta": {
                "source": "tushare",
                "trade_date": f"{today[:4]}-{today[4:6]}-{today[6:8]}",
                "limit_data_source": "derived",
                "updated_at": old.isoformat(),
            },
            "overview": {"limit_up_count": 1},
        }
        harness = SyncHarness(FakeDerivedClient())
        harness.database.get_snapshot = lambda *_args, **_kwargs: copy.deepcopy(snapshot)
        payload = harness.get_dashboard(today)
        self.assertEqual(payload["meta"]["data_status"], "partial")
        self.assertTrue(harness.database.saved)

    def test_official_catchup_skips_complete_today_snapshot(self):
        today = date.today().strftime("%Y%m%d")
        iso = f"{today[:4]}-{today[4:6]}-{today[6:8]}"
        due = official_catchup_due(
            today,
            {"meta": {"trade_date": iso, "limit_data_source": "official"}},
        )
        derived_due = official_catchup_due(
            today,
            {"meta": {"trade_date": iso, "limit_data_source": "derived"}},
        )
        now = datetime.now().astimezone().time().replace(tzinfo=None)
        if datetime.strptime("15:05", "%H:%M").time() <= now < datetime.strptime("22:00", "%H:%M").time():
            self.assertFalse(due)
            self.assertTrue(derived_due)
        else:
            self.assertFalse(due)
            self.assertFalse(derived_due)


class FrontendRefreshCopyTests(unittest.TestCase):
    def test_dashboard_script_distinguishes_partial_from_failure(self):
        script = (Path(__file__).resolve().parents[1] / "frontend" / "shared" / "dashboard.js").read_text(encoding="utf-8")
        self.assertIn("今日数据正在准备，当前展示", script)
        self.assertIn("部分正式数据尚未到齐", script)
        self.assertIn('job.status === "failed"', script)
        failed_block = script.split("if (job.status === \"failed\")", 1)[1].split("const query", 1)[0]
        self.assertIn("后台刷新失败", failed_block)
        success_block = script.split("const freshness = dashboardFreshnessMessage(meta);", 1)[1]
        self.assertNotIn("后台刷新失败", success_block.split("} else {", 1)[0])


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