from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from typing import Any

from backend.data.contracts import DataUsage
from backend.data.datahub import DatahubAwareTushareClient, DatahubBridge, DatahubClient, DatahubSettings
from backend.data.policy import DataSourcePolicy
from backend.data.providers import IfindProvider, TushareProvider
from backend.data.quality import DataQualityGate, QualityEvidence, QualityReport
from backend.data.providers.ifind_client import IfindHttpClient
from backend.data.providers.tushare_client import TushareClient
from backend.data.realtime import WebRealtimeAggregator
from backend.features.market.charts import EastmoneyChartClient, MarketChartClient


@dataclass(frozen=True)
class DataGateway:
    policy: DataSourcePolicy
    quality: DataQualityGate
    tushare_provider: TushareProvider
    ifind_provider: IfindProvider
    chart_data: MarketChartClient
    realtime_observer: WebRealtimeAggregator
    datahub: DatahubBridge

    @property
    def ifind(self) -> IfindHttpClient:
        return self.ifind_provider.client

    def tushare(
        self,
        dataset_id: str = "",
        usage: DataUsage = "calculation",
    ) -> TushareClient:
        if dataset_id:
            self.policy.assert_allowed(dataset_id, "tushare", usage)
        return DatahubAwareTushareClient(self.tushare_provider.client(), self.datahub)

    def dataset_status(self, trade_date: str) -> list[dict[str, Any]] | None:
        return self.datahub.dataset_status(trade_date)

    def batches(self, trade_date: str, dataset: str = "") -> list[dict[str, Any]] | None:
        return self.datahub.batches(trade_date, dataset)

    def assert_source(self, dataset_id: str, provider_id: str, usage: DataUsage) -> None:
        self.policy.assert_allowed(dataset_id, provider_id, usage)

    def provider_chain(self, dataset_id: str, usage: DataUsage) -> tuple[str, ...]:
        dataset = self.policy.dataset(dataset_id)
        allowed = []
        for provider_id in dataset.providers:
            try:
                self.policy.assert_allowed(dataset_id, provider_id, usage)
            except Exception:
                continue
            allowed.append(provider_id)
        if not allowed:
            raise RuntimeError(f"No permitted provider for {dataset_id} ({usage})")
        return tuple(allowed)

    def require_quality(
        self,
        evidence: QualityEvidence,
        usage: DataUsage,
        as_of: str | datetime | None = None,
    ) -> QualityReport:
        return self.quality.require(evidence, usage, as_of)


def build_data_gateway(
    credentials: dict[str, object],
    tushare_token_supplier: Callable[[], str] | None = None,
    datahub_settings: DatahubSettings | None = None,
) -> DataGateway:
    ifind = IfindHttpClient(
        str(credentials.get("ifind_refresh_token") or ""),
        str(credentials.get("ifind_access_token") or ""),
    )
    token_supplier = tushare_token_supplier or (
        lambda: str(credentials.get("tushare_token") or "")
    )
    policy = DataSourcePolicy.load()
    settings = datahub_settings or DatahubSettings.load(credentials=credentials)
    datahub_client = DatahubClient(settings)
    return DataGateway(
        policy=policy,
        quality=DataQualityGate.load(policy),
        tushare_provider=TushareProvider(token_supplier),
        ifind_provider=IfindProvider(ifind),
        chart_data=MarketChartClient(ifind, EastmoneyChartClient()),
        realtime_observer=WebRealtimeAggregator(),
        datahub=DatahubBridge(settings, datahub_client),
    )
