from __future__ import annotations

from pathlib import Path
from types import SimpleNamespace
import csv
import sys
import types

import pytest

from worai.core import structured_data_inventory as mod
from worai.errors import UsageError


def test_normalize_helpers_and_record_conversion() -> None:
    assert mod._normalize_loader("auto") == "web_scrape_api"
    assert mod._normalize_yes_no(True) == "yes"
    assert mod._normalize_yes_no(False) == "no"
    assert mod._normalize_yes_no("true") == "yes"
    assert mod._normalize_yes_no("0") == "no"

    assert mod._normalize_types(["A", "B"]) == "A,B"
    assert mod._normalize_types("A") == "A"

    assert mod._normalize_structured_data({"@graph": []}) == '{"@graph":[]}'

    row = mod._record_to_row(
        {
            "url": "https://example.com/a",
            "faq_markup": True,
            "faq_markup_from_graph": "no",
            "types": ["WebPage", "Thing"],
            "structured_data": {"@context": "https://schema.org", "@graph": []},
        }
    )
    assert row.url == "https://example.com/a"
    assert row.faq_markup == "yes"
    assert row.faq_markup_from_graph == "no"
    assert row.types == "WebPage,Thing"
    assert row.structured_data.startswith("{")


def test_build_source_bundle_modes(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    urls_file = tmp_path / "urls.txt"
    urls_file.write_text("https://example.com/a\n", encoding="utf-8")

    bundle = mod._build_source_bundle(
        mod.InventoryOptions(
            source=str(urls_file),
            api_key="k",
            output=str(tmp_path / "o.csv"),
            ingest_source="urls",
            ingest_loader="simple",
            ingest_passthrough_when_html=False,
            ingest_timeout_ms=10000,
            ingest_retry_attempts=1,
            ingest_retry_backoff_ms=0,
            playwright_wait_until="domcontentloaded",
            url_regex="/a",
        )
    )
    assert bundle["INGEST_SOURCE"] == "urls"
    assert bundle["INGEST_LOADER"] == "simple"
    assert bundle["INGEST_PASSTHROUGH_WHEN_HTML"] is False
    assert bundle["INGEST_TIMEOUT_MS"] == 10000
    assert bundle["INGEST_RETRY_ATTEMPTS"] == 1
    assert bundle["INGEST_RETRY_BACKOFF_MS"] == 0
    assert bundle["PLAYWRIGHT_WAIT_UNTIL"] == "domcontentloaded"
    assert bundle["URL_REGEX"] == "/a"
    assert bundle["URLS"] == ["https://example.com/a"]

    bundle = mod._build_source_bundle(
        mod.InventoryOptions(
            source="https://example.com/sitemap.xml",
            api_key="k",
            output=str(tmp_path / "o.csv"),
            ingest_source="sitemap",
        )
    )
    assert bundle["SITEMAP_URL"] == "https://example.com/sitemap.xml"

    monkeypatch.setattr(
        mod,
        "resolve_url_records",
        lambda opts: [SimpleNamespace(url="https://example.com/sheet-a", html=None, metadata=None)]
        if opts.ingest_source == "sheets"
        else [SimpleNamespace(url="https://example.com/local-a", html="<html/>", metadata={"k": "v"})],
    )

    bundle = mod._build_source_bundle(
        mod.InventoryOptions(
            source="https://docs.google.com/spreadsheets/d/12345678901234567890/edit",
            source_sheet_name="URLs",
            api_key="k",
            output=str(tmp_path / "o.csv"),
            ingest_source="sheets",
            client_secrets="/tmp/client.json",
            token="/tmp/token.json",
        )
    )
    assert bundle["INGEST_SOURCE"] == "urls"
    assert bundle["URLS"] == ["https://example.com/sheet-a"]

    local_file = tmp_path / "debug.ttl"
    local_file.write_text("", encoding="utf-8")
    bundle = mod._build_source_bundle(
        mod.InventoryOptions(
            source=str(local_file),
            api_key="k",
            output=str(tmp_path / "o.csv"),
            ingest_source="local",
        )
    )
    assert bundle["INGEST_SOURCE"] == "local"
    assert bundle["INGEST_LOCAL_ITEMS"] == [
        {"id": "local:0", "url": "https://example.com/local-a", "html": "<html/>", "metadata": {"k": "v"}}
    ]

    with pytest.raises(UsageError, match="INGEST_SOURCE=urls requires a local URL list file source"):
        mod._build_source_bundle(
            mod.InventoryOptions(
                source="https://example.com/page",
                api_key="k",
                output=str(tmp_path / "o.csv"),
                ingest_source="urls",
            )
        )


def test_sheet_helpers_and_google_writer(monkeypatch: pytest.MonkeyPatch) -> None:
    class _Session:
        def __init__(self):
            self.posts = []
            self.puts = []

        def get(self, url):
            if "fields=sheets.properties.title" in url:
                return SimpleNamespace(status_code=200, json=lambda: {"sheets": [{"properties": {"title": "Existing"}}]})
            return SimpleNamespace(status_code=500, text="boom", json=lambda: {})

        def post(self, url, json):
            self.posts.append((url, json))
            if url.endswith(":batchUpdate"):
                return SimpleNamespace(status_code=200, text="ok")
            return SimpleNamespace(status_code=200, text="ok")

        def put(self, url, json):
            self.puts.append((url, json))
            return SimpleNamespace(status_code=200, text="ok")

    s = _Session()
    assert mod._get_sheet_titles(s, "sheet") == ["Existing"]

    mod._ensure_sheet_exists(s, "sheet", "Existing")
    assert not s.posts
    mod._ensure_sheet_exists(s, "sheet", "NewSheet")
    assert s.posts

    monkeypatch.setattr(mod, "load_google_credentials", lambda **_k: object())
    monkeypatch.setattr(mod, "AuthorizedSession", lambda _creds: s)

    mod.write_google_sheet(
        "sheet",
        "NewSheet",
        [mod.InventoryRow(url="u", faq_markup="no", faq_markup_from_graph="no", types="", structured_data="{}")],
        client_secrets=None,
        token="t.json",
        port=8080,
    )
    assert s.puts


def test_sheet_error_paths_and_run_inventory_destination(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    class _BadSession:
        def get(self, _url):
            return SimpleNamespace(status_code=500, text="bad", json=lambda: {})

        def post(self, _url, json):
            return SimpleNamespace(status_code=500, text="bad")

        def put(self, _url, json):
            return SimpleNamespace(status_code=500, text="bad")

    with pytest.raises(RuntimeError, match="Failed to read spreadsheet metadata"):
        mod._get_sheet_titles(_BadSession(), "sheet")

    monkeypatch.setattr(mod, "load_google_credentials", lambda **_k: object())
    monkeypatch.setattr(mod, "AuthorizedSession", lambda _creds: _BadSession())
    with pytest.raises(RuntimeError):
        mod.write_google_sheet(
            "sheet",
            "S",
            [mod.InventoryRow(url="u", faq_markup="no", faq_markup_from_graph="no", types="", structured_data="{}")],
            client_secrets=None,
            token="t",
            port=8080,
        )

    monkeypatch.setattr(
        mod,
        "build_inventory",
        lambda _o, on_progress=None: [
            mod.InventoryRow(url="u", faq_markup="no", faq_markup_from_graph="no", types="", structured_data="{}")
        ],
    )
    seen = {}
    monkeypatch.setattr(mod, "write_google_sheet", lambda *args, **kwargs: seen.setdefault("called", True))
    out = tmp_path / "i.csv"
    rows = mod.run_inventory(mod.InventoryOptions(source="s", api_key="k", output=str(out)))
    assert rows and out.exists()

    # Verify CSV schema stays stable.
    with out.open("r", encoding="utf-8") as handle:
        reader = csv.DictReader(handle)
        assert reader.fieldnames == ["url", "faq_markup", "faq_markup_from_graph", "types", "structured_data"]

    mod.run_inventory(mod.InventoryOptions(source="s", api_key="k", destination_spreadsheet_id="id", destination_sheet_name="name"))
    assert seen.get("called") is True


def test_infer_source_mode_branches(tmp_path: Path) -> None:
    assert mod._infer_source_mode("https://docs.google.com/spreadsheets/d/12345678901234567890/edit", None) == "sheets"
    assert mod._infer_source_mode("https://example.com/sitemap.xml", None) == "sitemap"
    assert mod._infer_source_mode("custom-source", "URLs") == "sheets"

    xml_file = tmp_path / "s.xml"
    xml_file.write_text("", encoding="utf-8")
    assert mod._infer_source_mode(str(xml_file), None) == "sitemap"

    gz_file = tmp_path / "s.gz"
    gz_file.write_text("", encoding="utf-8")
    assert mod._infer_source_mode(str(gz_file), None) == "sitemap"

    ttl_file = tmp_path / "d.ttl"
    ttl_file.write_text("", encoding="utf-8")
    assert mod._infer_source_mode(str(ttl_file), None) == "local"

    json_file = tmp_path / "d.json"
    json_file.write_text("[]", encoding="utf-8")
    assert mod._infer_source_mode(str(json_file), None) == "local"

    txt_file = tmp_path / "u.txt"
    txt_file.write_text("", encoding="utf-8")
    assert mod._infer_source_mode(str(txt_file), None) == "urls"

    with pytest.raises(UsageError, match="Cannot infer ingest source"):
        mod._infer_source_mode("not-a-source", None)


def test_build_source_bundle_unsupported_source(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    monkeypatch.setattr(
        mod,
        "resolve_ingest_settings",
        lambda **_kwargs: SimpleNamespace(source="unsupported", loader="auto", passthrough_when_html=True),
    )
    with pytest.raises(UsageError, match="Unsupported ingest source"):
        mod._build_source_bundle(
            mod.InventoryOptions(
                source="https://example.com/sitemap.xml",
                api_key="k",
                output=str(tmp_path / "o.csv"),
            )
        )


def test_build_inventory_error_wrap_and_temp_cleanup(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    pkg = types.ModuleType("wordlift_sdk")
    ing = types.ModuleType("wordlift_sdk.ingestion")
    ing.create_structured_data_inventory_from_ingestion = lambda **_kwargs: (_ for _ in ()).throw(RuntimeError("boom"))
    pkg.ingestion = ing
    monkeypatch.setitem(sys.modules, "wordlift_sdk", pkg)
    monkeypatch.setitem(sys.modules, "wordlift_sdk.ingestion", ing)
    monkeypatch.setattr(mod, "_build_source_bundle", lambda _o: {"INGEST_SOURCE": "sitemap", "INGEST_LOADER": "simple"})

    temp_csv = tmp_path / "temp.csv"
    temp_csv.write_text("", encoding="utf-8")

    class _TempFile:
        def __init__(self, name: str):
            self.name = name

        def close(self):
            return None

    monkeypatch.setattr(mod.tempfile, "NamedTemporaryFile", lambda **_kwargs: _TempFile(str(temp_csv)))

    with pytest.raises(
        UsageError,
        match="Failed to create structured-data inventory.*RuntimeError: boom",
    ):
        mod.build_inventory(
            mod.InventoryOptions(
                source="https://example.com/sitemap.xml",
                api_key="k",
                output=None,
            )
        )
    assert temp_csv.exists() is False


def test_google_sheet_writer_branch_failures(monkeypatch: pytest.MonkeyPatch) -> None:
    rows = [mod.InventoryRow(url="u", faq_markup="no", faq_markup_from_graph="no", types="", structured_data="{}")]
    monkeypatch.setattr(mod, "load_google_credentials", lambda **_k: object())

    class _CreateFailSession:
        def get(self, _url):
            return SimpleNamespace(status_code=200, json=lambda: {"sheets": []})

        def post(self, url, json):
            if url.endswith(":batchUpdate"):
                return SimpleNamespace(status_code=500, text="bad-create")
            return SimpleNamespace(status_code=200, text="ok")

        def put(self, _url, json):
            return SimpleNamespace(status_code=200, text="ok")

    monkeypatch.setattr(mod, "AuthorizedSession", lambda _creds: _CreateFailSession())
    with pytest.raises(RuntimeError, match="Failed to create destination sheet"):
        mod.write_google_sheet("sheet", "Tab", rows, client_secrets=None, token="t", port=8080)

    class _ClearFailSession:
        def get(self, _url):
            return SimpleNamespace(status_code=200, json=lambda: {"sheets": [{"properties": {"title": "Tab"}}]})

        def post(self, _url, json):
            return SimpleNamespace(status_code=500, text="bad-clear")

        def put(self, _url, json):
            return SimpleNamespace(status_code=200, text="ok")

    monkeypatch.setattr(mod, "AuthorizedSession", lambda _creds: _ClearFailSession())
    with pytest.raises(RuntimeError, match="Failed to clear spreadsheet"):
        mod.write_google_sheet("sheet", "Tab", rows, client_secrets=None, token="t", port=8080)

    class _WriteFailSession:
        def get(self, _url):
            return SimpleNamespace(status_code=200, json=lambda: {"sheets": [{"properties": {"title": "Tab"}}]})

        def post(self, _url, json):
            return SimpleNamespace(status_code=200, text="ok")

        def put(self, _url, json):
            return SimpleNamespace(status_code=500, text="bad-write")

    monkeypatch.setattr(mod, "AuthorizedSession", lambda _creds: _WriteFailSession())
    with pytest.raises(RuntimeError, match="Failed to write spreadsheet"):
        mod.write_google_sheet("sheet", "Tab", rows, client_secrets=None, token="t", port=8080)
