from __future__ import annotations

import json
from pathlib import Path

import pytest

from worai.core import canonicalize_duplicate_pages as canon
from worai.core import dedupe
from worai.core import link_groups


def test_canonicalize_helpers_and_run(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    assert canon.normalize_name("  A   B ") == "a b"
    assert canon.parse_float("3.2") == 3.2
    assert canon.parse_float("x") == 0.0

    query = canon.build_entity_query(["https://example.com/a"])
    assert "entity(url" in query

    types = canon.extract_types([{"iri": "http://schema.org/Product"}, "Other"])
    assert "http://schema.org/Product" in types
    assert canon.choose_primary_type(["b", "a"]) == "a"

    label, matcher = canon.build_type_matcher("schema:Product")
    assert label == "Product"
    assert matcher is not None and matcher(["http://schema.org/Product"])

    selected = canon.select_entity_for_url(
        "u",
        [{"name": "n", "types": ["http://schema.org/Product"]}],
        "Product",
        matcher,
    )
    assert selected is not None

    with pytest.raises(Exception):
        canon.select_entity_for_url("u", [{"name": "a"}, {"name": "b"}], None, None)

    clusters = canon.build_clusters(
        {"u1": {"name": "Product A", "types": ["http://schema.org/Product"]}},
        "Product",
    )
    assert len(clusters) == 1

    out = tmp_path / "out.csv"
    canon.write_output_csv(str(out), [])
    assert out.read_text().startswith("type,name")

    in_csv = tmp_path / "gsc.csv"
    in_csv.write_text("page,clicks_28d\nhttps://example.com/a,10\nhttps://example.com/b,5\n", encoding="utf-8")
    monkeypatch.setattr(
        canon,
        "fetch_entities_by_url",
        lambda *_a, **_k: {
            "https://example.com/a": {"name": "X", "types": ["http://schema.org/Product"]},
            "https://example.com/b": {"name": "X", "types": ["http://schema.org/Product"]},
        },
    )
    opts = canon.CanonicalizeOptions(
        api_key="wl",
        input_csv=str(in_csv),
        output_csv=str(out),
        url_regex=None,
        entity_type="Product",
    )
    canon.run(opts)
    assert "duplicate_url" in out.read_text()


def test_fetch_entities_by_url_uses_graphql(monkeypatch: pytest.MonkeyPatch) -> None:
    def _stub(_endpoint, _key, _query):
        return {"data": {"e0": {"iri": "i1"}}}

    monkeypatch.setattr(canon, "graphql_request", _stub)
    out = canon.fetch_entities_by_url("e", "k", ["https://example.com/a"], batch_size=1)
    assert out["https://example.com/a"]["iri"] == "i1"


def test_dedupe_fetch_prompt_delete_and_run(monkeypatch: pytest.MonkeyPatch) -> None:
    class _Resp:
        status_code = 200
        text = "ok"

        @staticmethod
        def json():
            return {"data": {"entities": [{"iri": "i1", "url": "u"}, {"iri": "i2", "url": "u"}]}}

    monkeypatch.setattr(dedupe.requests, "post", lambda *_a, **_k: _Resp())
    entities = dedupe.fetch_entities("e", "k")
    assert len(entities) == 2

    inputs = iter(["s", "1"])
    monkeypatch.setattr("builtins.input", lambda _prompt: next(inputs))
    idx, skipped = dedupe.prompt_choice("u", ["i1", "i2"])
    assert skipped
    idx, skipped = dedupe.prompt_choice("u", ["i1", "i2"])
    assert idx == 0 and not skipped

    monkeypatch.setattr(dedupe.requests, "delete", lambda *_a, **_k: type("R", (), {"status_code": 204, "text": ""})())
    monkeypatch.setattr(dedupe.time, "sleep", lambda _x: None)
    assert dedupe.delete_iri("i1", "k", dry_run=False, rate_delay=0.1)

    monkeypatch.setattr(dedupe, "fetch_entities", lambda *_a, **_k: [{"iri": "i1", "url": "u"}, {"iri": "i2", "url": "u"}])
    monkeypatch.setattr(dedupe, "prompt_choice", lambda *_a, **_k: (0, False))
    monkeypatch.setattr(dedupe, "delete_iri", lambda *_a, **_k: True)
    dedupe.run(dedupe.DedupeOptions(api_key="k"))


def test_link_groups_helpers(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    iri, ident = link_groups.get_link_group_iri_and_id("https://example.com/lg/1/link/2")
    assert iri.endswith("/1/link")
    assert ident == "link"

    sess = link_groups.requests_retry_session()
    assert hasattr(sess, "mount")

    class _Session:
        def delete(self, *_a, **_k):
            return type("R", (), {"status_code": 204, "raise_for_status": lambda self: None})()

        def put(self, *_a, **_k):
            return type("R", (), {"status_code": 201, "raise_for_status": lambda self: None})()

        def patch(self, *_a, **_k):
            return type("R", (), {"status_code": 200, "raise_for_status": lambda self: None})()

    assert "curl -X DELETE" in link_groups.delete_link_group("https://example.com/lg/1", "k", _Session(), dry_run=True)
    assert link_groups.delete_link_group("https://example.com/lg/1", "k", _Session(), dry_run=False) is None

    g = link_groups.Graph()
    lg_iri = link_groups.URIRef("https://example.com/lg/1")
    assert "curl -X PUT" in link_groups.create_link_group((lg_iri, g), "k", _Session(), dry_run=True)
    assert link_groups.create_link_group((lg_iri, g), "k", _Session(), dry_run=False) is None

    assert "curl -L -X PATCH" in link_groups.patch_webpage(("https://example.com/p", "https://example.com/lg/1"), "k", _Session(), dry_run=True)
    assert link_groups.patch_webpage(("https://example.com/p", "https://example.com/lg/1"), "k", _Session(), dry_run=False) is None

    link_groups.run_concurrently(lambda _i: None, [1, 2], "x", 2)

    csv_path = tmp_path / "links.csv"
    csv_path.write_text(
        "source_iri,target_link_iri,target_web_page_iri,target_headline,target_url,target_position,target_score,target_anchor_text\n"
        "https://example.com/p,https://example.com/lg/1/l1,https://example.com/t,Headline,https://example.com/t,1,0.5,anchor\n",
        encoding="utf-8",
    )
    full_graph, webpages, api_graphs = link_groups.convert_csv_to_rdf(str(csv_path))
    assert len(full_graph) > 0
    assert len(webpages) == 1
    assert len(api_graphs) == 1

    monkeypatch.setattr(link_groups, "convert_csv_to_rdf", lambda _f: (full_graph, webpages, api_graphs))
    monkeypatch.setattr(link_groups, "apply_changes", lambda *_a, **_k: None)
    assert link_groups.run(link_groups.LinkGroupsOptions(input_file=str(csv_path), output_format="turtle"))
    assert link_groups.run(link_groups.LinkGroupsOptions(input_file=str(csv_path), apply=True, api_key="k")) == ""
