# parsly/management/commands/load_parser_fields.py
# -*- coding: utf-8 -*-
import json
import os
from typing import Any, Dict, List

from django.core.management.base import BaseCommand, CommandError
from django.db import transaction, IntegrityError

from parsly.models import ManualParserSelector


REQUIRED_KEYS = {"fieldtype", "label", "description", "options"}
ALLOWED_KEYS = REQUIRED_KEYS | {"example", "id"}  # id z JSON-a ignorujemy


def _validate_item(item: Dict[str, Any], idx: int):
    if not isinstance(item, dict):
        raise CommandError(f"Element #{idx} nie jest obiektem JSON.")
    missing = REQUIRED_KEYS - set(item.keys())
    if missing:
        raise CommandError(f"Element #{idx} brakuje pól: {', '.join(sorted(missing))}")
    extra = set(item.keys()) - ALLOWED_KEYS
    # nie blokujemy na extra, ale można ostrzec
    return {
        "fieldtype": str(item["fieldtype"]).strip(),
        "label": str(item["label"]).strip(),
        "description": str(item["description"]).strip(),
        "options": item["options"],
        "example": item.get("example"),
    }


class Command(BaseCommand):
    help = "Ładuje definicje typów pól parsera z pliku JSON (upsert po fieldtype)."

    def add_arguments(self, parser):
        parser.add_argument(
            "--path",
            default="/data/fields.json",
            help="Ścieżka do pliku JSON z definicjami (domyślnie /data/fields.json)",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Pokaż co zostanie zrobione, ale nic nie zapisuj.",
        )
        parser.add_argument(
            "--purge-missing",
            action="store_true",
            help="Usuń z bazy rekordy, których nie ma w pliku JSON.",
        )

    def handle(self, *args, **opts):
        path = opts["path"]
        dry_run = opts["dry_run"]
        purge_missing = opts["purge_missing"]

        if not os.path.exists(path):
            raise CommandError(f"Plik nie istnieje: {path}")

        with open(path, "r", encoding="utf-8") as f:
            try:
                payload = json.load(f)
            except json.JSONDecodeError as e:
                raise CommandError(f"Błąd JSON w {path}: {e}")

        if not isinstance(payload, list):
            raise CommandError("Plik musi zawierać listę obiektów.")

        items: List[Dict[str, Any]] = []
        for i, raw in enumerate(payload, start=1):
            items.append(_validate_item(raw, i))

        # pokaż plan
        self.stdout.write(self.style.NOTICE(f"Znaleziono {len(items)} definicji w pliku."))

        # mapy pomocnicze
        file_fieldtypes = {it["fieldtype"] for it in items}
        db_fieldtypes = set(ManualParserSelector.objects.values_list("fieldtype", flat=True))

        to_create = [it for it in items if it["fieldtype"] not in db_fieldtypes]
        to_update = [it for it in items if it["fieldtype"] in db_fieldtypes]
        to_delete = db_fieldtypes - file_fieldtypes if purge_missing else set()

        self.stdout.write(f"Do utworzenia: {len(to_create)}")
        self.stdout.write(f"Do aktualizacji: {len(to_update)}")
        if purge_missing:
            self.stdout.write(f"Do usunięcia (purge): {len(to_delete)}")

        if dry_run:
            self.stdout.write(self.style.SUCCESS("Dry-run zakończony. Nic nie zapisano."))
            return

        try:
            with transaction.atomic():
                # upsert: create
                for it in to_create:
                    ManualParserSelector.objects.create(
                        fieldtype=it["fieldtype"],
                        label=it["label"],
                        description=it["description"],
                        options=it["options"],
                        example=it["example"],
                    )

                # upsert: update
                for it in to_update:
                    ManualParserSelector.objects.filter(fieldtype=it["fieldtype"]).update(
                        label=it["label"],
                        description=it["description"],
                        options=it["options"],
                        example=it["example"],
                    )

                # purge
                if to_delete:
                    ManualParserSelector.objects.filter(fieldtype__in=to_delete).delete()

        except IntegrityError as e:
            raise CommandError(f"Błąd transakcji: {e}")

        self.stdout.write(self.style.SUCCESS("Załadowano definicje typów pól."))

