from __future__ import annotations

import argparse
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import List

from . import __version__
from .bibtex_patch import apply_bibtex_patches
from .cache import Cache, NullCache
from .ingest import ingest_bibtex, ingest_docx, ingest_pdf, ingest_text
from .match import MatchConfig, verify_references
from .providers.dblp import DBLPProvider
from .providers.openlibrary import OpenLibraryProvider
from .providers.semanticscholar import SemanticScholarProvider
from .report import write_report_json, write_report_markdown
from .types import Report


def main() -> int:
    parser = argparse.ArgumentParser(prog="citesleuth", description="Verify citations and spot hallucinations.")
    parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
    subparsers = parser.add_subparsers(dest="command", required=True)

    verify_parser = subparsers.add_parser("verify", help="Verify citations in a file.")
    verify_parser.add_argument("path", help="Input file (.bib, .pdf, .docx, .txt)")
    verify_parser.add_argument("--out", default="report.json", help="Output JSON report path")
    verify_parser.add_argument("--format", default="both", choices=["json", "md", "both"], help="Report format")
    verify_parser.add_argument("--cache-path", default="~/.cache/citesleuth/cache.sqlite")
    verify_parser.add_argument("--offline", action="store_true")
    verify_parser.add_argument("--no-cache", action="store_true")
    verify_parser.add_argument("--api-key-semanticscholar", default=os.getenv("SEMANTIC_SCHOLAR_API_KEY"))
    verify_parser.add_argument("--user-agent", default="CiteSleuth/0.1 (contact@example.com)")
    verify_parser.add_argument("--max-rps", type=float, default=None)
    verify_parser.add_argument("--provider-delay", default=None)
    verify_parser.add_argument("--polite", action="store_true")
    verify_parser.add_argument("--apply-fixes", action="store_true")
    verify_parser.add_argument("--write-bib", default=None, help="Write updated BibTeX file (BibTeX input only)")
    verify_parser.add_argument("--only", choices=["unverified", "conflict", "likely", "verified"], default=None)
    verify_parser.add_argument("--max-entries", type=int, default=None)
    verify_parser.add_argument("--verified-threshold", type=float, default=0.85)
    verify_parser.add_argument("--likely-threshold", type=float, default=0.7)
    verify_parser.add_argument("--overwrite-threshold", type=float, default=0.9)

    args = parser.parse_args()

    if args.command == "verify":
        return _run_verify(args)

    parser.error("Unknown command")
    return 1


def _run_verify(args: argparse.Namespace) -> int:
    path = args.path
    try:
        refs = _load_refs(path)
    except Exception as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2
    if args.max_entries:
        refs = refs[: args.max_entries]
    if not refs:
        print("warning: no references extracted from input.", file=sys.stderr)

    cache = NullCache() if args.no_cache else Cache(args.cache_path)
    provider_delays = _parse_provider_delays(args.provider_delay)
    if args.polite and args.max_rps is None:
        args.max_rps = 1.0
    global_delay = (1.0 / args.max_rps) if args.max_rps and args.max_rps > 0 else None
    providers = [
        SemanticScholarProvider(cache, offline=args.offline, user_agent=args.user_agent, api_key=args.api_key_semanticscholar),
        DBLPProvider(cache, offline=args.offline, user_agent=args.user_agent),
        OpenLibraryProvider(cache, offline=args.offline, user_agent=args.user_agent),
    ]
    _apply_provider_delays(providers, provider_delays, global_delay)

    config = MatchConfig(
        verified_threshold=args.verified_threshold,
        likely_threshold=args.likely_threshold,
        overwrite_threshold=args.overwrite_threshold,
    )
    results = verify_references(refs, providers, config)

    if args.only:
        results = [r for r in results if r.verdict.lower() == args.only]

    summary = _summarize(results)
    report = Report(
        tool_version=__version__,
        timestamp=datetime.now(timezone.utc).isoformat(),
        config={
            "offline": args.offline,
            "cache_path": args.cache_path,
            "no_cache": args.no_cache,
            "max_rps": args.max_rps,
            "provider_delay": provider_delays,
            "verified_threshold": args.verified_threshold,
            "likely_threshold": args.likely_threshold,
            "overwrite_threshold": args.overwrite_threshold,
            "apply_fixes": args.apply_fixes,
        },
        summary=summary,
        results=results,
    )

    out_path = Path(args.out)
    if args.format in {"json", "both"}:
        write_report_json(report, str(out_path))
    if args.format in {"md", "both"}:
        md_path = out_path.with_suffix(".md")
        write_report_markdown(report, str(md_path))

    if args.apply_fixes or args.write_bib:
        if Path(path).suffix.lower() != ".bib":
            print("apply-fixes/write-bib only supported for BibTeX inputs.", file=sys.stderr)
        else:
            output_path = args.write_bib or str(Path(path).with_suffix(".verified.bib"))
            apply_bibtex_patches(
                path,
                results,
                output_path,
                apply_fixes=args.apply_fixes,
                overwrite_threshold=args.overwrite_threshold,
            )

    return 0


def _load_refs(path: str):
    suffix = Path(path).suffix.lower()
    if suffix == ".bib":
        return ingest_bibtex(path)
    if suffix == ".pdf":
        return ingest_pdf(path)
    if suffix == ".docx":
        return ingest_docx(path)
    if suffix == ".txt":
        return ingest_text(path)
    raise ValueError(f"Unsupported file type: {suffix}")


def _summarize(results: List) -> dict:
    counts = {}
    missing_counts = {}
    for result in results:
        counts[result.verdict] = counts.get(result.verdict, 0) + 1
        for field in result.patch.keys():
            missing_counts[field] = missing_counts.get(field, 0) + 1
    issues = {
        "Most common missing fields": [],
        "Likely hallucinations": [],
        "Conflicts": [],
    }
    if missing_counts:
        ranked = sorted(missing_counts.items(), key=lambda item: item[1], reverse=True)
        issues["Most common missing fields"] = [f"{field} ({count})" for field, count in ranked[:5]]
    for result in results:
        if result.verdict == "UNVERIFIED":
            issues["Likely hallucinations"].append(result.ref_id)
        if result.verdict == "CONFLICT":
            issues["Conflicts"].append(result.ref_id)
    return {"counts": counts, "issues": issues}


def _parse_provider_delays(value: str | None) -> dict:
    if not value:
        return {}
    delays = {}
    for chunk in value.split(","):
        if not chunk.strip():
            continue
        name, _, delay = chunk.partition("=")
        if not name or not delay:
            continue
        try:
            delays[name.strip()] = float(delay)
        except ValueError:
            continue
    return delays


def _apply_provider_delays(providers, provider_delays: dict, global_delay: float | None) -> None:
    for provider in providers:
        if provider.provider_name in provider_delays:
            provider.min_delay_seconds = provider_delays[provider.provider_name]
        if global_delay is not None:
            provider.min_delay_seconds = max(provider.min_delay_seconds, global_delay)


if __name__ == "__main__":
    raise SystemExit(main())
