from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
import csv
import json
import shutil
import sqlite3

from models import Identity, QUALITY_NAMES


@dataclass(frozen=True)
class CatalogIdentity:
    category: str
    prefix: str
    base: str
    suffix: str

    def key(self) -> str:
        return "|".join((self.category, self.prefix, self.base, self.suffix))


@dataclass(frozen=True)
class VariantSelection:
    identity: CatalogIdentity
    quality: str = "Zwykły"
    upgrade: int = 0
    legendary: bool = False
    player_level: int = 80

    @property
    def class_id(self) -> int:
        try:
            rank = QUALITY_NAMES.index(self.quality)
        except ValueError:
            rank = 0
        return rank * 6 + max(0, min(5, int(self.upgrade)))

    def cache_key(self) -> str:
        ident = self.identity
        return "|".join(
            (
                ident.category,
                ident.prefix,
                ident.base,
                ident.suffix,
                self.quality,
                str(self.upgrade),
                "1" if self.legendary else "0",
                str(self.player_level),
            )
        )


class ItemCatalog:
    """Lazy structural catalogue of every legal prefix/base/suffix identity.

    Quality, upgrade, legendary status and player level are variant dimensions and
    deliberately are not materialised in memory. This keeps the app quick while
    still representing the complete 1,080,816-variant space as a database view.
    """

    def __init__(self, rules):
        self.rules = rules
        self._rows: list[CatalogIdentity] | None = None

    def identities(self) -> list[CatalogIdentity]:
        if self._rows is None:
            rows: list[CatalogIdentity] = []
            for category in self.rules.categories():
                prefixes = [""] + list(self.rules.prefixes(category))
                suffixes = [""] + list(self.rules.suffixes(category))
                for base in self.rules.bases(category):
                    for prefix in prefixes:
                        for suffix in suffixes:
                            rows.append(CatalogIdentity(category, prefix, base, suffix))
            self._rows = rows
        return self._rows

    def identity_count(self) -> int:
        return len(self.identities())

    def variant_count(self) -> int:
        return self.identity_count() * 24 * 2

    def filter(
        self,
        *,
        category: str = "",
        prefix_semantic: str = "",
        base: str = "",
        suffix: str = "",
        query: str = "",
    ) -> list[CatalogIdentity]:
        q = query.casefold().strip()
        out = []
        for row in self.identities():
            if category and row.category != category:
                continue
            if prefix_semantic == "__EMPTY__" and row.prefix:
                continue
            if prefix_semantic and prefix_semantic != "__EMPTY__" and self.rules.prefix_semantic_key(row.prefix) != prefix_semantic:
                continue
            if base and row.base != base:
                continue
            if suffix == "__EMPTY__" and row.suffix:
                continue
            if suffix and suffix != "__EMPTY__" and row.suffix != suffix:
                continue
            if q:
                display = self.rules.identity_display(
                    Identity(row.category, row.base, row.prefix, row.suffix, False)
                ).casefold()
                if q not in display:
                    continue
            out.append(row)
        return out

    def statistics(self) -> list[dict]:
        stats = []
        for category in self.rules.categories():
            b = len(self.rules.bases(category))
            p = len(self.rules.prefixes(category)) + 1
            s = len(self.rules.suffixes(category)) + 1
            identities = b * p * s
            stats.append(
                {
                    "category": category,
                    "label": self.rules.category_label(category),
                    "bases": b,
                    "prefixes_with_blank": p,
                    "suffixes_with_blank": s,
                    "identities": identities,
                    "variants": identities * 48,
                }
            )
        return stats

    def export_sqlite(self, destination: Path) -> Path:
        destination = Path(destination)
        destination.parent.mkdir(parents=True, exist_ok=True)
        if destination.exists():
            destination.unlink()
        conn = sqlite3.connect(destination)
        try:
            conn.executescript(
                """
                PRAGMA journal_mode=WAL;
                CREATE TABLE metadata(key TEXT PRIMARY KEY, value TEXT NOT NULL);
                CREATE TABLE categories(
                    category TEXT PRIMARY KEY,
                    display_name TEXT NOT NULL,
                    base_count INTEGER NOT NULL,
                    prefix_count_with_blank INTEGER NOT NULL,
                    suffix_count_with_blank INTEGER NOT NULL,
                    identity_count INTEGER NOT NULL,
                    variant_count INTEGER NOT NULL
                );
                CREATE TABLE item_identities(
                    id INTEGER PRIMARY KEY,
                    category TEXT NOT NULL,
                    category_name TEXT NOT NULL,
                    prefix_key TEXT NOT NULL,
                    prefix_name TEXT NOT NULL,
                    base_key TEXT NOT NULL,
                    base_name TEXT NOT NULL,
                    suffix_key TEXT NOT NULL,
                    suffix_name TEXT NOT NULL,
                    normal_name TEXT NOT NULL,
                    UNIQUE(category, prefix_key, base_key, suffix_key)
                );
                CREATE INDEX idx_identity_components
                    ON item_identities(category, prefix_key, base_key, suffix_key);
                CREATE TABLE quality_levels(
                    class_id INTEGER PRIMARY KEY,
                    quality TEXT NOT NULL,
                    upgrade INTEGER NOT NULL,
                    level_label TEXT NOT NULL
                );
                CREATE TABLE legendary_values(
                    legendary INTEGER PRIMARY KEY,
                    label TEXT NOT NULL
                );
                CREATE VIEW all_variants AS
                SELECT i.id AS identity_id,
                       i.category, i.category_name,
                       i.prefix_key, i.prefix_name,
                       i.base_key, i.base_name,
                       i.suffix_key, i.suffix_name,
                       q.class_id, q.quality, q.upgrade, q.level_label,
                       l.legendary, l.label AS legendary_label
                FROM item_identities i
                CROSS JOIN quality_levels q
                CROSS JOIN legendary_values l;
                """
            )
            metadata = {
                "catalog": "BloodWars Moria item catalogue",
                "identity_count": str(self.identity_count()),
                "variant_count": str(self.variant_count()),
                "source": "https://r21.bloodwars.pl/test_items.php/",
                "note": "Exact rendered statistics are cached separately by the app on demand.",
            }
            conn.executemany("INSERT INTO metadata(key,value) VALUES(?,?)", metadata.items())
            conn.executemany(
                "INSERT INTO categories VALUES(?,?,?,?,?,?,?)",
                [
                    (
                        s["category"], s["label"], s["bases"],
                        s["prefixes_with_blank"], s["suffixes_with_blank"],
                        s["identities"], s["variants"],
                    )
                    for s in self.statistics()
                ],
            )
            rows = []
            for idx, ident in enumerate(self.identities(), 1):
                identity = Identity(ident.category, ident.base, ident.prefix, ident.suffix, False)
                rows.append(
                    (
                        idx,
                        ident.category,
                        self.rules.category_label(ident.category),
                        ident.prefix,
                        self.rules.prefix_filter_label(ident.prefix) if ident.prefix else "Brak",
                        ident.base,
                        self.rules.display(ident.base),
                        ident.suffix,
                        self.rules.display(ident.suffix) if ident.suffix else "Brak",
                        self.rules.identity_display(identity),
                    )
                )
            conn.executemany(
                """INSERT INTO item_identities(
                    id,category,category_name,prefix_key,prefix_name,base_key,base_name,
                    suffix_key,suffix_name,normal_name
                ) VALUES(?,?,?,?,?,?,?,?,?,?)""",
                rows,
            )
            quality_rows = []
            for rank, quality in enumerate(QUALITY_NAMES):
                for upgrade in range(6):
                    class_id = rank * 6 + upgrade
                    label = quality + (f" +{upgrade}" if upgrade else "")
                    quality_rows.append((class_id, quality, upgrade, label))
            conn.executemany("INSERT INTO quality_levels VALUES(?,?,?,?)", quality_rows)
            conn.executemany(
                "INSERT INTO legendary_values VALUES(?,?)",
                [(0, "NIE"), (1, "TAK")],
            )
            conn.commit()
        finally:
            conn.close()
        return destination

    def export_csv(self, destination: Path) -> Path:
        destination = Path(destination)
        destination.parent.mkdir(parents=True, exist_ok=True)
        with destination.open("w", newline="", encoding="utf-8-sig") as fh:
            writer = csv.writer(fh, delimiter=";")
            writer.writerow(
                [
                    "Kategoria", "Prefix key", "Prefix", "Baza key", "Baza",
                    "Suffix key", "Suffix", "Nazwa zwykła",
                ]
            )
            for ident in self.identities():
                item = Identity(ident.category, ident.base, ident.prefix, ident.suffix, False)
                writer.writerow(
                    [
                        self.rules.category_label(ident.category),
                        ident.prefix,
                        self.rules.prefix_filter_label(ident.prefix) if ident.prefix else "Brak",
                        ident.base,
                        self.rules.display(ident.base),
                        ident.suffix,
                        self.rules.display(ident.suffix) if ident.suffix else "Brak",
                        self.rules.identity_display(item),
                    ]
                )
        return destination
