# scraper2_hj3415/app/nfs/composition.py
from __future__ import annotations

from dataclasses import dataclass

from db2_hj3415.common.types import MongoDoc
from pymongo.asynchronous.database import AsyncDatabase
from scraper2_hj3415.app.nfs.adapters.out.playwright.browser_factory import (
    PlaywrightBrowserFactory,
)
from scraper2_hj3415.app.nfs.adapters.out.sinks.nfs_memory_sink import NfsMemorySink
from scraper2_hj3415.app.nfs.adapters.out.sinks.nfs_mongo_sink import NfsMongoSink
from scraper2_hj3415.app.nfs.adapters.out.sinks.store import InMemoryStore
from scraper2_hj3415.app.nfs.domain.types import Sink
from scraper2_hj3415.app.nfs.ports.browser.browser_factory_port import (
    BrowserFactoryPort,
)
from scraper2_hj3415.app.nfs.ports.sinks.nfs_sink_port import NfsSinkPort
from scraper2_hj3415.app.nfs.services.fetch.fetch_c101 import FetchC101
from scraper2_hj3415.app.nfs.services.fetch.fetch_c103 import FetchC103
from scraper2_hj3415.app.nfs.services.fetch.fetch_c104 import FetchC104
from scraper2_hj3415.app.nfs.services.fetch.fetch_c106 import FetchC106
from scraper2_hj3415.app.nfs.services.fetch.fetch_c108 import FetchC108
from scraper2_hj3415.app.nfs.usecases.ingest.ingest_c101 import IngestC101
from scraper2_hj3415.app.nfs.usecases.ingest.ingest_c103 import IngestC103
from scraper2_hj3415.app.nfs.usecases.ingest.ingest_c104 import IngestC104
from scraper2_hj3415.app.nfs.usecases.ingest.ingest_c106 import IngestC106
from scraper2_hj3415.app.nfs.usecases.ingest.ingest_c108 import IngestC108


@dataclass(frozen=True)
class NfsFetchUsecases:
    c101: FetchC101
    c103: FetchC103
    c104: FetchC104
    c106: FetchC106
    c108: FetchC108


@dataclass(frozen=True)
class NfsIngestUsecases:
    c101: IngestC101
    c103: IngestC103
    c104: IngestC104
    c106: IngestC106
    c108: IngestC108


@dataclass(frozen=True)
class NfsResources:
    """
    NFS 기능 전용 리소스.

    주의:
    - Mongo / DB는 앱 전체 공용 리소스이므로 여기서 소유하지 않는다.
    - NFS는 browser_factory와 sink(memory/mongo adapter)만 가진다.
    """

    sink: NfsSinkPort
    browser_factory: BrowserFactoryPort
    store: InMemoryStore[MongoDoc] | None = None

    async def aclose(self) -> None:
        await self.browser_factory.aclose()


@dataclass(frozen=True)
class NfsUsecases:
    fetch: NfsFetchUsecases
    ingest: NfsIngestUsecases
    resources: NfsResources


def build_browser_factory(
    *,
    headless: bool,
    timeout_ms: int,
    max_concurrency: int,
) -> BrowserFactoryPort:
    return PlaywrightBrowserFactory(
        headless=headless,
        timeout_ms=timeout_ms,
        max_concurrency=max_concurrency,
    )


def build_fetch_usecases(*, factory: BrowserFactoryPort) -> NfsFetchUsecases:
    return NfsFetchUsecases(
        c101=FetchC101(factory=factory),
        c103=FetchC103(factory=factory),
        c104=FetchC104(factory=factory),
        c106=FetchC106(factory=factory),
        c108=FetchC108(factory=factory),
    )


def build_nfs_sink(
    *,
    sink: Sink,
    db: AsyncDatabase[MongoDoc] | None,
) -> tuple[NfsSinkPort, InMemoryStore[MongoDoc] | None]:
    """
    NFS sink 조립.

    정책:
    - memory sink: InMemoryStore 생성 후 NfsMemorySink 사용
    - mongo sink: 상위 composition에서 전달받은 db를 사용해 NfsMongoSink 생성
    """
    match sink:
        case "memory":
            store: InMemoryStore[MongoDoc] = InMemoryStore()
            out_sink: NfsSinkPort = NfsMemorySink(store)
            return out_sink, store

        case "mongo":
            if db is None:
                raise ValueError("mongo sink requires db")
            out_sink = NfsMongoSink(db)
            return out_sink, None

        case _:
            raise ValueError(f"invalid sink kind: {sink!r}")


def build_resources(
    *,
    sink: Sink,
    factory: BrowserFactoryPort,
    db: AsyncDatabase[MongoDoc] | None,
) -> NfsResources:
    out_sink, store = build_nfs_sink(sink=sink, db=db)
    return NfsResources(
        sink=out_sink,
        browser_factory=factory,
        store=store,
    )


def build_ingest_usecases(
    *,
    fetch: NfsFetchUsecases,
    sink: NfsSinkPort,
) -> NfsIngestUsecases:
    return NfsIngestUsecases(
        c101=IngestC101(fetch=fetch.c101, sink=sink),
        c103=IngestC103(fetch=fetch.c103, sink=sink),
        c104=IngestC104(fetch=fetch.c104, sink=sink),
        c106=IngestC106(fetch=fetch.c106, sink=sink),
        c108=IngestC108(fetch=fetch.c108, sink=sink),
    )


def build_nfs_usecases(
    *,
    sink: Sink,
    headless: bool,
    timeout_ms: int,
    max_concurrency: int,
    db: AsyncDatabase[MongoDoc] | None = None,
) -> NfsUsecases:
    """
    NFS 기능 전용 composition helper.

    주의:
    - 환경변수는 여기서 읽지 않는다.
    - DB도 여기서 생성하지 않는다.
    - 필요한 설정값과 db는 상위 composition root에서 주입한다.
    """
    factory = build_browser_factory(
        headless=headless,
        timeout_ms=timeout_ms,
        max_concurrency=max_concurrency,
    )
    fetch = build_fetch_usecases(factory=factory)
    resources = build_resources(
        sink=sink,
        factory=factory,
        db=db,
    )
    ingest = build_ingest_usecases(fetch=fetch, sink=resources.sink)

    return NfsUsecases(
        fetch=fetch,
        ingest=ingest,
        resources=resources,
    )
