import asyncio
import csv
import hashlib
from urllib.parse import quote

import requests
from dataclasses import dataclass

import wordlift_client
from rdflib import Graph, Namespace, URIRef, Literal
from rdflib.namespace import RDF
from rich.console import Console
from rich.table import Table

from wordlift_client import ApiClient, Configuration

WORDLIFT_API_URL = "https://api.wordlift.io"
DEFAULT_GQL_URL = "https://api.wordlift.io/graphql"
DEFAULT_GQL_QUERY = """\
query url_to_iri($urls: [String!]!) {
  entities(query: { urlConstraint: { in: $urls } }) {
    iri
    url: string(name: "schema:url")
    type: strings(name: "rdf:type")
  }
}
"""
SCHEMA = Namespace("http://schema.org/")
EYEWEAR = Namespace("https://purl.archive.org/purl/eyewear/")


@dataclass
class DilImportOptions:
    api_key: str
    csv_file: str


def _build_client(api_key: str) -> ApiClient:
    config = Configuration(host=WORDLIFT_API_URL)
    config.api_key["ApiKey"] = api_key
    config.api_key_prefix["ApiKey"] = "Key"
    return ApiClient(config)


def _auth_headers(api_key):
    return {
        "Authorization": f"Key {api_key}",
        "X-include-private": "true",
        "Content-Type": "application/json",
    }


async def fetch_account(api_key, api_client):
    api = wordlift_client.AccountApi(api_client)
    result = await api.get_me()
    return result.dataset_uri


def _extract_entities(payload):
    data = payload.get("data") or {}
    entities = data.get("entities") or data.get("entity") or data.get("items") or []
    if isinstance(entities, dict):
        entities = [entities]
    if not isinstance(entities, list):
        return []
    return entities


def _normalize_types(types):
    if types is None:
        return []
    if isinstance(types, list):
        return [t for t in types if isinstance(t, str)]
    return []


def _is_collection_page_only(types):
    normalized = _normalize_types(types)
    if not normalized:
        return False
    allowed = {
        "http://schema.org/CollectionPage",
    }
    return all(t in allowed for t in normalized)


def _sha1_url(url):
    return hashlib.sha1(url.encode("utf-8")).hexdigest()


def _build_collection_page_iri(dataset_uri, url):
    base = dataset_uri.rstrip("/")
    return f"{base}/collection-pages/{_sha1_url(url)}"


def fetch_entities_for_urls(api_key, urls):

    payload = {"query": DEFAULT_GQL_QUERY, "variables": {"urls": urls}}
    resp = requests.post(
        DEFAULT_GQL_URL, headers=_auth_headers(api_key), json=payload, timeout=30
    )
    resp.raise_for_status()

    return resp.json()


def resolve_iris_for_urls(api_key: str, dataset_uri: str, urls: list[str]):
    payload = fetch_entities_for_urls(api_key, urls)
    entities = _extract_entities(payload)
    by_url = {}
    for entity in entities:
        url = entity.get("url")
        if not url:
            continue
        by_url.setdefault(url, []).append(entity)

    resolved = {}
    reused = set()
    for url in urls:
        for entity in by_url.get(url, []):
            if _is_collection_page_only(entity.get("type")):
                iri = entity.get("iri")
                if iri:
                    resolved[url] = iri
                    reused.add(url)
                    break
        if url not in resolved:
            resolved[url] = _build_collection_page_iri(dataset_uri, url)
    return resolved, reused


def load_rows(path):
    with open(path, mode="r", encoding="utf-8-sig", newline="") as handle:
        reader = csv.DictReader(handle)
        rows = list(reader)
        fieldnames = reader.fieldnames or []
    return rows, fieldnames


def build_graph(rows):
    g = Graph()
    g.bind("schema", SCHEMA)
    g.bind("eyewear", EYEWEAR)

    grouped = {}
    for row in rows:
        source_iri = row.get("source_iri") or ""
        if not source_iri:
            continue
        grouped.setdefault(source_iri, []).append(row)

    for source_iri, entries in grouped.items():
        source_uri = URIRef(source_iri)
        g.add((source_uri, RDF.type, SCHEMA.CollectionPage))

        source_url = entries[0].get("source_url") or ""
        if source_url:
            g.add((source_uri, SCHEMA.mainEntityOfPage, Literal(source_url)))
            g.add((source_uri, SCHEMA.url, Literal(source_url)))

        for entry in entries:
            target_iri = entry.get("target_iri") or ""
            if not target_iri:
                continue
            target_uri = URIRef(target_iri)

            g.add((target_uri, RDF.type, EYEWEAR.CategoryRelatedLinks))
            g.add(
                (
                    target_uri,
                    EYEWEAR.categoryRelatedLinkName,
                    Literal(entry.get("target_anchor_text") or ""),
                )
            )
            g.add(
                (
                    target_uri,
                    EYEWEAR.categoryRelatedLinkOrder,
                    Literal(str(entry.get("target_position") or "")),
                )
            )
            g.add(
                (
                    target_uri,
                    EYEWEAR.categoryRelatedLinkUrl,
                    Literal(entry.get("target_url") or ""),
                )
            )

            target_url = entry.get("target_url") or ""
            if target_url:
                g.add((source_uri, SCHEMA.relatedLink, Literal(target_url)))

            g.add((source_uri, EYEWEAR.categoryRelatedLinks, target_uri))

    return g


async def import_dils(options: DilImportOptions) -> int:
    console = Console()
    api_client = _build_client(options.api_key)

    try:
        dataset_uri = await fetch_account(options.api_key, api_client)

        rows, fieldnames = load_rows(options.csv_file)

        if "source_url" not in fieldnames:
            raise ValueError("Input CSV is missing required column: 'source_url'")

        source_urls = sorted(
            {row["source_url"] for row in rows if row.get("source_url")}
        )
        unique_url_count = len(source_urls)

        source_iri_map, reused_urls = resolve_iris_for_urls(
            options.api_key, dataset_uri, source_urls
        )
        reused_entities_count = len(reused_urls)

        for f in ["source_iri", "target_iri"]:
            if f not in fieldnames:
                fieldnames.append(f)

        for row in rows:
            url = row.get("source_url")
            s_iri = source_iri_map.get(url, "")
            row["source_iri"] = s_iri

            pos = str(row.get("target_position") or "").strip()
            if s_iri and pos:
                row["target_iri"] = (
                    f"{s_iri.rstrip('/')}/category-related-links/{quote(pos)}"
                )
            else:
                row["target_iri"] = ""

        graph = build_graph(rows)
        api = wordlift_client.EntitiesApi(api_client)

        await api.create_or_update_entities(
            graph.serialize(format="turtle"), _content_type="text/turtle"
        )

        table = Table(title="Import Summary", show_header=False, border_style="green")
        table.add_row("Source URLs processed", str(unique_url_count))
        table.add_row(
            "Reused CollectionPage entities",
            f"[bold green]{reused_entities_count}[/bold green]",
        )
        table.add_row(
            "New CollectionPage entities created",
            f"[bold yellow]{unique_url_count - reused_entities_count}[/bold yellow]",
        )

        console.print(table)
    finally:
        await api_client.close()

    return 0


def run(options: DilImportOptions) -> int:
    return asyncio.run(import_dils(options))
