# scraper2_hj3415/app/nfs/usecases/ingest/ingest_c106.py
from __future__ import annotations

from collections.abc import Iterable
from datetime import datetime

from common_hj3415.utils.time import utcnow
from contracts_hj3415.common.envelope import NfsEnvelopeDTO, make_nfs_envelope
from contracts_hj3415.nfs.c106 import C106Contents, C106Labels, C106ValuesMap
from logging_hj3415 import logger
from scraper2_hj3415.app.nfs.domain.blocks import MetricsBlock
from scraper2_hj3415.app.nfs.domain.constants import get_block_keys
from scraper2_hj3415.app.nfs.domain.doc import NfsDoc
from scraper2_hj3415.app.nfs.domain.endpoint import EndpointKind
from scraper2_hj3415.app.nfs.ports.ingest.nfs_ingest_port import NfsIngestPort
from scraper2_hj3415.app.nfs.ports.sinks.nfs_sink_port import NfsSinkPort
from scraper2_hj3415.app.nfs.services.fetch.fetch_c106 import FetchC106

endpoint_kind = EndpointKind.C106
endpoint = endpoint_kind.value


def _metricsblock_to_c106_contents_map(block: MetricsBlock) -> dict[str, C106ValuesMap]:
    """
    MetricsBlock(domain) -> dict[MetricKey, dict[CodeKey, Num]]
    """
    out: dict[str, C106ValuesMap] = {}
    for mk, series in block.metrics.items():
        out[str(mk)] = dict(series.values)  # Mapping -> dict
    return out


def c106_doc_to_envelope(*, doc: NfsDoc, asof: datetime) -> NfsEnvelopeDTO:
    """
    NfsDoc(domain, endpoint=c106) -> NfsEnvelopeDTO(contracts)

    - contents: {"y": {metric: {code: num}}, "q": {...}}
    - payload.meta.labels: {"y": {metric: raw_label}, "q": {...}}
    """
    # 1) 기본 골격 (규약 안정성)
    contents: C106Contents = {"y": {}, "q": {}}
    labels: C106Labels = {"y": {}, "q": {}}

    # 2) 도메인 블록키 기준으로 채우기
    for bk in get_block_keys(endpoint_kind):
        bd = doc.blocks.get(bk)
        if bd is None:
            continue

        if not isinstance(bd, MetricsBlock):
            raise TypeError(
                f"c106 expects MetricsBlock, got {type(bd).__name__} | block_key={bk!r}"
            )

        metric_map = _metricsblock_to_c106_contents_map(bd)
        label_map = dict(doc.labels.get(bk, {}))  # 없으면 {}

        if bk == "y":
            contents["y"] = metric_map
            labels["y"] = label_map
        elif bk == "q":
            contents["q"] = metric_map
            labels["q"] = label_map
        else:
            raise ValueError(f"invalid c106 block key: {bk!r}")

    # 3) Envelope 생성
    # - endpoint/contents/labels는 payload 안(meta 포함)으로 들어감
    # - C106은 labels를 meta.labels에 둔다
    env = make_nfs_envelope(
        code=doc.code,
        asof=asof,
        endpoint=endpoint,
        contents=contents,
        labels=labels,
    )
    return env


class IngestC106(NfsIngestPort):
    fetch: FetchC106
    sink: NfsSinkPort

    def __init__(self, fetch: FetchC106, sink: NfsSinkPort):
        self.fetch = fetch
        self.sink = sink

    async def execute(
        self,
        code: str,
        *,
        sleep_sec: float = 2.0,
        asof: datetime | None = None,
    ) -> NfsEnvelopeDTO:
        asof = asof or utcnow()

        doc = await self.fetch.execute(code, sleep_sec=sleep_sec)
        logger.debug(f"doc:\n{doc}")
        if doc is None:
            raise RuntimeError(f"c106 fetch returned None: code={code}")

        env = c106_doc_to_envelope(doc=doc, asof=asof)
        logger.debug(f"env:\n{env}")

        await self.sink.write(env)
        return env

    async def execute_many(
        self,
        codes: Iterable[str],
        *,
        sleep_sec: float = 2.0,
        asof: datetime | None = None,
    ) -> list[NfsEnvelopeDTO]:
        batch_asof = asof or utcnow()

        docs = await self.fetch.execute_many(codes, sleep_sec=sleep_sec)
        envs = [c106_doc_to_envelope(doc=d, asof=batch_asof) for d in docs]
        logger.debug(f"envs:\n{envs}")

        await self.sink.write_many(envs)
        return envs
