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

from collections.abc import Iterable
from datetime import datetime
from typing import Any

from common_hj3415.utils.time import utcnow
from contracts_hj3415.common.envelope import NfsEnvelopeDTO, make_nfs_envelope
from contracts_hj3415.nfs.c103 import C103ValuesMap
from contracts_hj3415.nfs.types import MetricKey
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_c103 import FetchC103

endpoint_kind = EndpointKind.C103
endpoint = endpoint_kind.value  # "c103"


def _metricsblock_to_contents(block: MetricsBlock) -> dict[MetricKey, C103ValuesMap]:
    """
    MetricsBlock(domain) -> dict[MetricKey, dict[PeriodKey, Num]]
    """
    out: dict[MetricKey, C103ValuesMap] = {}
    for mk, series in block.metrics.items():
        out[mk] = dict(series.values)
    return out


def c103_doc_to_envelope(*, doc: NfsDoc, asof: datetime) -> NfsEnvelopeDTO:
    """
    NfsDoc(domain,c103) -> NfsEnvelopeDTO(표준)

    - payload.contents: C103Contents (6개 블록 항상 유지)
    - payload.meta.labels: C103Labels(중첩 dict) 그대로 저장 (Mapping[str,Any] 가정)
    """
    # 1) contents 기본 골격(규약 안정성)
    contents: dict[str, dict[str, Any]] = {  # pyright: ignore[reportExplicitAny]
        "손익계산서y": {},
        "손익계산서q": {},
        "재무상태표y": {},
        "재무상태표q": {},
        "현금흐름표y": {},
        "현금흐름표q": {},
    }

    # 2) labels도 동일하게 블록별 중첩 유지
    labels: dict[str, dict[str, Any]] = {  # pyright: ignore[reportExplicitAny]
        "손익계산서y": {},
        "손익계산서q": {},
        "재무상태표y": {},
        "재무상태표q": {},
        "현금흐름표y": {},
        "현금흐름표q": {},
    }

    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"c103 expects MetricsBlock, got {type(bd).__name__} | block_key={bk!r}"
            )

        bk_str = str(bk)
        if bk_str not in contents:
            # 계약에 없는 block은 무시(또는 raise 정책 택1)
            continue

        contents[bk_str] = _metricsblock_to_contents(bd)
        labels[bk_str] = dict(doc.labels.get(bk, {}))

    return make_nfs_envelope(
        code=doc.code,
        asof=asof,
        endpoint=endpoint,
        contents=contents,
        labels=labels,
        meta=None,
    )


class IngestC103(NfsIngestPort):
    fetch: FetchC103
    sink: NfsSinkPort

    def __init__(self, fetch: FetchC103, 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)
        if doc is None:
            raise RuntimeError(f"c103 fetch returned None: code={code}")

        env = c103_doc_to_envelope(doc=doc, asof=asof)
        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 = [c103_doc_to_envelope(doc=d, asof=batch_asof) for d in docs]

        await self.sink.write_many(envs)
        return envs
