from __future__ import annotations

from pathlib import Path
import sys
import types

import pytest
from typer.testing import CliRunner

from worai import cli as root_cli
from worai.commands import web_pages as web_pages_cmd
from worai.core.web_pages import WebPagesClassifyTypesOptions, run_classify_types
from worai.errors import UsageError


def _install_fake_sdk_ingestion(monkeypatch: pytest.MonkeyPatch, callback) -> None:
    pkg = types.ModuleType("wordlift_sdk")
    mod = types.ModuleType("wordlift_sdk.ingestion")
    mod.create_type_classification_csv_from_ingestion = callback
    pkg.ingestion = mod
    monkeypatch.setitem(sys.modules, "wordlift_sdk", pkg)
    monkeypatch.setitem(sys.modules, "wordlift_sdk.ingestion", mod)


def test_run_classify_types_urls_source_bundle(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    seen: dict[str, object] = {}

    def _fake_create(**kwargs):
        seen["kwargs"] = kwargs
        return [1]

    _install_fake_sdk_ingestion(monkeypatch, _fake_create)

    source = tmp_path / "urls.txt"
    source.write_text("https://example.com/a\nhttps://example.com/b\n", encoding="utf-8")

    result = run_classify_types(
        WebPagesClassifyTypesOptions(
            source=str(source),
            output_csv=str(tmp_path / "out.csv"),
            ingest_source="urls",
            ingest_loader="playwright",
            url_regex=r"/a$",
            agent_cli="codex",
            agent_timeout_sec=90.0,
            max_markdown_chars=9000,
        )
    )

    assert result == [1]
    kwargs = seen["kwargs"]
    assert kwargs["output_csv"] == str(tmp_path / "out.csv")
    assert kwargs["agent_cli"] == "codex"
    assert kwargs["agent_timeout_sec"] == 90.0
    assert kwargs["max_markdown_chars"] == 9000
    bundle = kwargs["source_bundle"]
    assert bundle["INGEST_SOURCE"] == "urls"
    assert bundle["INGEST_LOADER"] == "playwright"
    assert bundle["URL_REGEX"] == r"/a$"
    assert bundle["URLS"] == ["https://example.com/a", "https://example.com/b"]


def test_run_classify_types_auto_infers_sitemap(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    seen: dict[str, object] = {}

    def _fake_create(**kwargs):
        seen["kwargs"] = kwargs
        return []

    _install_fake_sdk_ingestion(monkeypatch, _fake_create)

    run_classify_types(
        WebPagesClassifyTypesOptions(
            source="https://example.com/sitemap.xml",
            output_csv=str(tmp_path / "out.csv"),
            ingest_source=None,
            ingest_loader=None,
        )
    )
    bundle = seen["kwargs"]["source_bundle"]
    assert bundle["INGEST_SOURCE"] == "sitemap"
    assert bundle["INGEST_LOADER"] == "web_scrape_api"
    assert "INGEST_TIMEOUT_MS" not in bundle
    assert "PLAYWRIGHT_WAIT_UNTIL" not in bundle
    assert bundle["SITEMAP_URL"] == "https://example.com/sitemap.xml"


def test_run_classify_types_sheets_requires_sheet_and_service_account(tmp_path: Path) -> None:
    with pytest.raises(UsageError, match="--sheet-name is required"):
        run_classify_types(
            WebPagesClassifyTypesOptions(
                source="https://docs.google.com/spreadsheets/d/12345678901234567890/edit",
                output_csv=str(tmp_path / "out.csv"),
                ingest_source="sheets",
            )
        )

    with pytest.raises(UsageError, match="SHEETS_SERVICE_ACCOUNT is required"):
        run_classify_types(
            WebPagesClassifyTypesOptions(
                source="https://docs.google.com/spreadsheets/d/12345678901234567890/edit",
                output_csv=str(tmp_path / "out.csv"),
                ingest_source="sheets",
                sheet_name="URLs",
            )
        )


def test_web_pages_command_wires_options(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    seen: dict[str, object] = {}

    def _fake_run(options, on_progress=None):
        assert callable(on_progress)
        seen["options"] = options
        return [1, 2]

    monkeypatch.setattr(web_pages_cmd, "run_classify_types", _fake_run)
    monkeypatch.setattr(
        web_pages_cmd,
        "load_profile_settings",
        lambda _ctx: (
            {
                "ingest.timeout_ms": "9000",
                "ingest.retry_attempts": "2",
                "ingest.retry_backoff_ms": "100",
                "ingest.playwright_wait_until": "domcontentloaded",
            },
            "default",
        ),
    )

    result = CliRunner().invoke(
        root_cli.app,
        [
            "web-pages",
            "classify-types",
            "https://example.com/sitemap.xml",
            "-y",
            "--ingest-source",
            "sitemap",
            "--ingest-loader",
            "playwright",
            "--url-regex",
            "car-insurance",
            "--output",
            str(tmp_path / "types.csv"),
        ],
    )

    assert result.exit_code == 0
    assert "Wrote 2 rows" in result.output
    options = seen["options"]
    assert options.source == "https://example.com/sitemap.xml"
    assert options.ingest_source == "sitemap"
    assert options.ingest_loader == "playwright"
    assert options.url_regex == "car-insurance"
    assert options.ingest_timeout_ms == 9000
    assert options.ingest_retry_attempts == 2
    assert options.ingest_retry_backoff_ms == 100
    assert options.playwright_wait_until == "domcontentloaded"


def test_root_cli_includes_web_pages_command() -> None:
    result = CliRunner().invoke(root_cli.app, ["--help"])
    assert result.exit_code == 0
    assert "web-pages" in result.output
    assert "Run web pages ingestion workflows." in result.output


def test_web_pages_classify_types_confirmation_abort(monkeypatch: pytest.MonkeyPatch) -> None:
    called = {"value": False}

    def _fake_run(_options, on_progress=None):
        called["value"] = True
        return []

    monkeypatch.setattr(web_pages_cmd, "run_classify_types", _fake_run)
    monkeypatch.setattr(web_pages_cmd, "load_profile_settings", lambda _ctx: ({}, "default"))

    result = CliRunner().invoke(
        root_cli.app,
        ["web-pages", "classify-types", "https://example.com/sitemap.xml"],
        input="n\n",
    )

    assert result.exit_code == 0
    assert "consume agent credits" in result.output
    assert "Aborted." in result.output
    assert called["value"] is False


def test_web_pages_classify_types_confirmation_default_yes(monkeypatch: pytest.MonkeyPatch) -> None:
    called = {"value": False}

    def _fake_run(_options, on_progress=None):
        assert callable(on_progress)
        called["value"] = True
        return [1]

    monkeypatch.setattr(web_pages_cmd, "run_classify_types", _fake_run)
    monkeypatch.setattr(web_pages_cmd, "load_profile_settings", lambda _ctx: ({}, "default"))

    result = CliRunner().invoke(
        root_cli.app,
        ["web-pages", "classify-types", "https://example.com/sitemap.xml"],
        input="\n",
    )

    assert result.exit_code == 0
    assert "consume agent credits" in result.output
    assert "Wrote 1 rows" in result.output
    assert called["value"] is True


def test_classify_progress_callback_lifecycle_and_zero_case() -> None:
    bars: list[object] = []

    class _Bar:
        def __init__(self, *, total, desc, unit, leave):
            self.total = total
            self.desc = desc
            self.unit = unit
            self.leave = leave
            self.updates: list[int] = []
            self.closed = False

        def update(self, delta: int):
            self.updates.append(delta)

        def refresh(self):
            return None

        def close(self):
            self.closed = True

    def _factory(**kwargs):
        bar = _Bar(**kwargs)
        bars.append(bar)
        return bar

    callback, close = web_pages_cmd._build_classify_progress_callback(
        enabled=True,
        progress_factory=_factory,
    )
    callback({"event": "type_classification.progress.started", "meta": {"total": 2}})
    callback({"event": "type_classification.progress.updated", "meta": {"completed": 1, "total": 2}})
    callback({"event": "type_classification.progress.updated", "meta": {"completed": 1, "total": 2}})
    callback({"event": "type_classification.progress.updated", "meta": {"completed": 2, "total": 2}})
    callback({"event": "type_classification.progress.completed", "meta": {"completed": 2, "total": 2}})
    close()

    assert len(bars) == 1
    assert bars[0].updates == [1, 1]
    assert bars[0].closed is True

    zero_bars: list[object] = []

    def _zero_factory(**kwargs):
        bar = _Bar(**kwargs)
        zero_bars.append(bar)
        return bar

    callback, close = web_pages_cmd._build_classify_progress_callback(
        enabled=True,
        progress_factory=_zero_factory,
    )
    callback({"event": "type_classification.progress.started", "meta": {"total": 0}})
    callback({"event": "type_classification.progress.updated", "meta": {"completed": 0, "total": 0}})
    callback({"event": "type_classification.progress.completed", "meta": {"completed": 0, "total": 0}})
    close()

    assert len(zero_bars) == 1
    assert zero_bars[0].updates == []
    assert zero_bars[0].closed is True


def test_web_pages_classify_types_error_update_logged_before_raise(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    def _fake_run(_options, on_progress=None):
        assert callable(on_progress)
        on_progress({"event": "type_classification.progress.started", "meta": {"total": 1}})
        on_progress(
            {
                "event": "type_classification.progress.updated",
                "meta": {
                    "status": "error",
                    "url": "https://example.com/page",
                    "error_type": "ExtractionError",
                    "error_message": "Could not parse content",
                    "completed": 1,
                    "total": 1,
                },
            }
        )
        raise UsageError("classification failed")

    monkeypatch.setattr(web_pages_cmd, "run_classify_types", _fake_run)
    monkeypatch.setattr(web_pages_cmd, "load_profile_settings", lambda _ctx: ({}, "default"))

    result = CliRunner().invoke(
        root_cli.app,
        ["web-pages", "classify-types", "https://example.com/sitemap.xml", "-y"],
    )

    assert result.exit_code != 0
    assert "Type classification failed url=https://example.com/page error_type=ExtractionError error_message=Could not parse content" in result.output
