"""Graph sync workflow integration."""

from __future__ import annotations

import asyncio
import json
import logging
import os
import re
import tempfile
import tomllib
from dataclasses import dataclass
from pathlib import Path
from typing import Any

try:
    from wordlift_sdk.configuration import ConfigurationProvider
    from wordlift_sdk.kg_build import (
        ProfileImportProtocol,
        load_profile_config,
    )
    from wordlift_sdk.kg_build.container import KgBuildApplicationContainer
except ModuleNotFoundError:  # pragma: no cover - environment dependent
    ConfigurationProvider = None
    ProfileImportProtocol = None
    load_profile_config = None
    KgBuildApplicationContainer = None


LOGGER = logging.getLogger("worai.graph")
_TIMEOUT_RE = re.compile(r"^(?P<value>\d+(?:\.\d+)?)(?P<unit>ms|s)?$")


@dataclass
class GraphSyncOptions:
    profile: str
    config_path: Path = Path("worai.toml")
    debug: bool = False


def load_service_account_json(raw_value: str | None) -> str:
    if not raw_value:
        raise ValueError("Missing profile setting: sheets_service_account")

    candidate_path = Path(raw_value)
    if candidate_path.exists() and candidate_path.is_file():
        return candidate_path.read_text(encoding="utf-8")

    try:
        parsed = json.loads(raw_value)
    except json.JSONDecodeError as exc:
        raise ValueError("sheets_service_account must be JSON content or a file path") from exc

    if not isinstance(parsed, dict):
        raise ValueError("sheets_service_account JSON must be an object")
    return raw_value


def build_extra_settings(profile_settings: dict[str, Any]) -> dict[str, Any]:
    consumed = {
        "urls",
        "sitemap_url",
        "sitemap_url_pattern",
        "sheets_url",
        "sheets_name",
        "sheets_service_account",
        "overwrite",
        "concurrency",
        "web_page_import_mode",
        "web_page_import_timeout",
        "google_search_console",
    }
    return {key.upper(): value for key, value in profile_settings.items() if key not in consumed}


def _append_py_setting(lines: list[str], key: str, value: Any) -> None:
    lines.append(f"{key} = {repr(value)}")


def _using_sheets_source(settings: dict[str, Any]) -> bool:
    return bool(settings.get("sheets_url") or settings.get("sheets_name"))


def _parse_bool(value: Any, *, name: str) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        lowered = value.strip().lower()
        if lowered in {"1", "true", "yes", "on"}:
            return True
        if lowered in {"0", "false", "no", "off"}:
            return False
    raise ValueError(f"{name} must be a boolean")


def _resolve_web_page_import_timeout_ms(value: Any) -> int:
    if value is None:
        return 60000

    parsed: float
    explicit_ms = False
    explicit_s = False
    if isinstance(value, (int, float)):
        parsed = float(value)
    elif isinstance(value, str):
        token = value.strip().lower()
        if not token:
            raise ValueError("web_page_import_timeout must not be empty")
        match = _TIMEOUT_RE.match(token)
        if not match:
            raise ValueError(
                "web_page_import_timeout must be a number of seconds (e.g. 60 or 60s) "
                "or milliseconds (e.g. 60000ms)"
            )
        parsed = float(match.group("value"))
        explicit_ms = match.group("unit") == "ms"
        explicit_s = match.group("unit") == "s"
    else:
        raise ValueError(
            "web_page_import_timeout must be numeric seconds or a string with optional s/ms suffix"
        )

    if parsed <= 0:
        raise ValueError("web_page_import_timeout must be greater than 0")

    if explicit_ms:
        return int(parsed)
    if explicit_s:
        return int(parsed * 1000)

    return int(parsed * 1000)


def resolve_google_search_console(config_path: Path, profile_name: str) -> bool:
    try:
        data = tomllib.loads(config_path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return False
    except tomllib.TOMLDecodeError as exc:
        raise ValueError(f"Invalid TOML config: {config_path}: {exc}") from exc

    global_value = data.get("google_search_console", False)
    profile_table = data.get("profile") or data.get("profiles") or {}
    profile_value = profile_table.get(profile_name, {}).get("google_search_console")
    selected = profile_value if profile_value is not None else global_value
    return _parse_bool(selected, name="google_search_console")


def resolve_postprocessor_runtime(config_path: Path, profile_name: str) -> str | None:
    try:
        data = tomllib.loads(config_path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return None
    except tomllib.TOMLDecodeError as exc:
        raise ValueError(f"Invalid TOML config: {config_path}: {exc}") from exc

    profile_table = data.get("profile") or data.get("profiles") or {}
    profile_value = profile_table.get(profile_name, {}).get("postprocessor_runtime")
    global_value = data.get("postprocessor_runtime")
    selected = profile_value if profile_value is not None else global_value
    if selected is None:
        return None
    if not isinstance(selected, str):
        raise ValueError("postprocessor_runtime must be a string")

    runtime = selected.strip().lower()
    if runtime not in {"subprocess", "persistent"}:
        raise ValueError("postprocessor_runtime must be one of: subprocess, persistent")
    return runtime


def _build_dynamic_settings(
    profile_api_key: str,
    settings: dict[str, Any],
    *,
    google_search_console: bool,
) -> tuple[list[str], str | None]:
    lines = [f"WORDLIFT_KEY = {repr(profile_api_key)}"]
    sheets_service_account_json: str | None = None

    urls = settings.get("urls")
    sitemap_url = settings.get("sitemap_url")
    sitemap_url_pattern = settings.get("sitemap_url_pattern")
    sheets_url = settings.get("sheets_url")
    sheets_name = settings.get("sheets_name")
    using_sheets = _using_sheets_source(settings)

    if urls:
        _append_py_setting(lines, "URLS", list(urls))
    elif sitemap_url:
        _append_py_setting(lines, "SITEMAP_URL", sitemap_url)
        if sitemap_url_pattern:
            _append_py_setting(lines, "SITEMAP_URL_PATTERN", sitemap_url_pattern)
    elif using_sheets:
        if not sheets_url or not sheets_name:
            raise ValueError("Both sheets_url and sheets_name are required when using Google Sheets source")
        sheets_service_account_json = load_service_account_json(settings.get("sheets_service_account"))
        _append_py_setting(lines, "SHEETS_URL", sheets_url)
        _append_py_setting(lines, "SHEETS_NAME", sheets_name)
    else:
        raise ValueError(
            "One source is required: urls, sitemap_url, or sheets_url+sheets_name+sheets_service_account"
        )

    _append_py_setting(lines, "WEB_PAGE_IMPORT_MODE", str(settings.get("web_page_import_mode", "premium_scraper")))
    _append_py_setting(
        lines,
        "WEB_PAGE_IMPORT_TIMEOUT",
        _resolve_web_page_import_timeout_ms(settings.get("web_page_import_timeout")),
    )
    _append_py_setting(lines, "CONCURRENCY", int(settings.get("concurrency", 4)))
    _append_py_setting(lines, "OVERWRITE", bool(settings.get("overwrite", False)))
    _append_py_setting(lines, "GOOGLE_SEARCH_CONSOLE", google_search_console)

    for key, value in build_extra_settings(settings).items():
        _append_py_setting(lines, key, value)

    return lines, sheets_service_account_json


async def _run_dynamic_cloud_workflow(
    *,
    lines: list[str],
    sheets_service_account_json: str | None,
    debug: bool,
    debug_profile_name: str,
    protocol_factory,
) -> None:
    temp_sa_path: str | None = None
    temp_config_path: str | None = None
    debug_dir = Path("output") / "debug_cloud" / debug_profile_name if debug else None

    try:
        if debug_dir:
            debug_dir.mkdir(parents=True, exist_ok=True)
            LOGGER.info("Debug mode enabled. Saving intermediate graphs to: %s", debug_dir)

        if sheets_service_account_json:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f_sa:
                f_sa.write(sheets_service_account_json)
                temp_sa_path = f_sa.name
            _append_py_setting(lines, "SHEETS_SERVICE_ACCOUNT", temp_sa_path)

        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f_cfg:
            f_cfg.write("\n".join(lines) + "\n")
            temp_config_path = f_cfg.name

        provider = ConfigurationProvider.create(temp_config_path)
        container = KgBuildApplicationContainer(provider)
        context = await container.get_context()
        protocol = protocol_factory(context, debug_dir=debug_dir, workflow_config=None)
        if hasattr(protocol, "__await__"):
            protocol = await protocol
        container.set_protocol(protocol)
        workflow = await container.create_kg_import_workflow()
        await workflow.run()
    finally:
        if temp_config_path and os.path.exists(temp_config_path):
            os.remove(temp_config_path)
        if temp_sa_path and os.path.exists(temp_sa_path):
            os.remove(temp_sa_path)


async def run_graph_sync_async(options: GraphSyncOptions) -> None:
    if (
        ConfigurationProvider is None
        or ProfileImportProtocol is None
        or load_profile_config is None
        or KgBuildApplicationContainer is None
    ):
        raise ValueError("wordlift-sdk kg_build support is unavailable in this Python environment")

    config = load_profile_config(options.config_path)
    profile = config.get(options.profile)
    if profile is None:
        raise ValueError(f"Profile '{options.profile}' not found in {options.config_path}")
    if not profile.api_key:
        raise ValueError(f"Profile '{profile.name}' is missing api_key")

    settings = dict(profile.settings)
    google_search_console = resolve_google_search_console(options.config_path, options.profile)
    postprocessor_runtime = resolve_postprocessor_runtime(options.config_path, options.profile)
    lines, sheets_service_account_json = _build_dynamic_settings(
        profile.api_key,
        settings,
        google_search_console=google_search_console,
    )

    async def protocol_factory(context, debug_dir=None, workflow_config=None):
        return ProfileImportProtocol(
            context=context,
            profile=profile,
            root_dir=Path.cwd(),
            debug_dir=debug_dir,
        )

    previous_postprocessor_runtime = os.environ.get("POSTPROCESSOR_RUNTIME")
    try:
        if postprocessor_runtime is not None:
            os.environ["POSTPROCESSOR_RUNTIME"] = postprocessor_runtime

        await _run_dynamic_cloud_workflow(
            lines=lines,
            sheets_service_account_json=sheets_service_account_json,
            debug=options.debug,
            debug_profile_name=profile.name,
            protocol_factory=protocol_factory,
        )
    finally:
        if postprocessor_runtime is not None:
            if previous_postprocessor_runtime is None:
                os.environ.pop("POSTPROCESSOR_RUNTIME", None)
            else:
                os.environ["POSTPROCESSOR_RUNTIME"] = previous_postprocessor_runtime


def run_graph_sync(options: GraphSyncOptions) -> None:
    asyncio.run(run_graph_sync_async(options))
