"""Searchable taxonomy index for auto-complete and discovery."""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Literal

from xc_dl.paths import data_dir

if TYPE_CHECKING:
    from xc_dl.api.client import XenoCantoClient
    from xc_dl.api.models import Recording


@dataclass
class GenusInfo:
    family: str = ""
    group: str = ""


@dataclass
class SpeciesInfo:
    en: str = ""
    genus: str = ""


@dataclass
class TaxonMatch:
    name: str
    type: Literal["species", "genus", "family"]
    tag: str  # e.g. gen:Tyto, fam:Strigidae, sp:"Tyto alba"


class TaxonomyIndex:
    """Searchable taxonomy index for auto-complete and discovery."""

    def __init__(self) -> None:
        self.genera: dict[str, GenusInfo] = {}
        self.species: dict[str, SpeciesInfo] = {}

    def load(self) -> None:
        """Load bundled data, then overlay user data."""
        self._load_bundled()
        self._load_user_data()

    def _load_bundled(self) -> None:
        """Load the bundled taxonomy data."""
        bundled_path = Path(__file__).parent.parent / "data" / "taxonomy.json"
        if not bundled_path.exists():
            return
        try:
            with open(bundled_path) as f:
                data = json.load(f)
        except Exception:
            return

        if "genera" in data:
            for genus, info in data["genera"].items():
                self.genera[genus] = GenusInfo(
                    family=info.get("family", ""),
                    group=info.get("group", ""),
                )
            for sp, info in data.get("species", {}).items():
                self.species[sp] = SpeciesInfo(
                    en=info.get("en", ""),
                    genus=info.get("genus", ""),
                )
        else:
            # Old flat format: {genus: family}
            for genus, family in data.items():
                self.genera[genus] = GenusInfo(family=family, group="birds")

    def _load_user_data(self) -> None:
        """Load user-grown taxonomy data from XDG data dir."""
        path = data_dir() / "taxonomy.json"
        if not path.exists():
            return
        try:
            with open(path) as f:
                data = json.load(f)
        except Exception:
            return

        for genus, info in data.get("genera", {}).items():
            self.genera[genus] = GenusInfo(
                family=info.get("family", ""),
                group=info.get("group", ""),
            )
        for sp, info in data.get("species", {}).items():
            self.species[sp] = SpeciesInfo(
                en=info.get("en", ""),
                genus=info.get("genus", ""),
            )

    def save_cache(self) -> None:
        """Write to $XDG_DATA_HOME/xc-dl/taxonomy.json."""
        path = data_dir() / "taxonomy.json"
        path.parent.mkdir(parents=True, exist_ok=True)
        data = {
            "genera": {
                g: {"family": info.family, "group": info.group}
                for g, info in sorted(self.genera.items())
            },
            "species": {
                sp: {"en": info.en, "genus": info.genus}
                for sp, info in sorted(self.species.items())
            },
        }
        try:
            path.write_text(json.dumps(data, indent=2))
        except Exception:
            pass

    def has_user_data(self) -> bool:
        """Check if user taxonomy data exists on disk."""
        return (data_dir() / "taxonomy.json").exists()

    def ingest(self, recordings: list[Recording]) -> int:
        """Extract new taxa from API recordings. Returns count of new entries."""
        new_count = 0
        for rec in recordings:
            genus = rec.gen
            species_name = rec.species_name
            if genus and genus not in self.genera:
                # We don't always have family info from the recording directly,
                # but we can try to look it up from bundled data
                family = ""
                group = rec.grp or ""
                # Check if we already know this genus from bundled data
                if genus in self.genera:
                    continue
                self.genera[genus] = GenusInfo(family=family, group=group)
                new_count += 1

            if species_name and species_name not in self.species:
                self.species[species_name] = SpeciesInfo(
                    en=rec.en or "",
                    genus=genus,
                )
                new_count += 1

        return new_count

    def search(self, query: str) -> list[TaxonMatch]:
        """Substring search across English names, species, genera, families.
        Returns sorted matches: prefix matches first, then contains."""
        if not query:
            return []

        q_lower = query.lower()
        results: list[tuple[int, TaxonMatch]] = []

        # Search genera
        for genus, info in self.genera.items():
            g_lower = genus.lower()
            if q_lower in g_lower:
                rank = 0 if g_lower.startswith(q_lower) else 1
                results.append((rank, TaxonMatch(
                    name=genus,
                    type="genus",
                    tag=f"gen:{genus}",
                )))

        # Search families
        seen_families: set[str] = set()
        for genus, info in self.genera.items():
            if not info.family or info.family in seen_families:
                continue
            seen_families.add(info.family)
            f_lower = info.family.lower()
            if q_lower in f_lower:
                rank = 0 if f_lower.startswith(q_lower) else 1
                results.append((rank, TaxonMatch(
                    name=info.family,
                    type="family",
                    tag=f"fam:{info.family}",
                )))

        # Search species (scientific + English names)
        for sp, info in self.species.items():
            sp_lower = sp.lower()
            en_lower = (info.en or "").lower()
            if q_lower in sp_lower:
                rank = 0 if sp_lower.startswith(q_lower) else 1
                results.append((rank, TaxonMatch(
                    name=f'{sp} ({info.en})' if info.en else sp,
                    type="species",
                    tag=f'sp:"{sp}"' if " " in sp else f"sp:{sp}",
                )))
            elif q_lower in en_lower:
                rank = 0 if en_lower.startswith(q_lower) else 1
                results.append((rank, TaxonMatch(
                    name=f'{info.en} ({sp})',
                    type="species",
                    tag=f'sp:"{sp}"' if " " in sp else f"sp:{sp}",
                )))

        results.sort(key=lambda x: (x[0], x[1].name.lower()))
        return [m for _, m in results[:50]]

    async def seed(self, client: XenoCantoClient) -> int:
        """First-launch seed: fetch one page per organism group. Returns new entry count."""
        groups = ["birds", "bats", "frogs", "grasshoppers", "land mammals"]
        total_new = 0
        for group in groups:
            query = f"grp:{group}" if " " not in group else f'grp:"{group}"'
            try:
                response = await client.fetch_page(query, page=1, per_page=500)
                total_new += self.ingest(list(response.recordings))
            except Exception:
                continue
        self.save_cache()
        return total_new
