import os
import shutil

from models.model import Model


def _maybe_migrate_tab_json_to_zone():
    """One-time copy ``db/tab.json`` → ``db/zone.json`` when upgrading."""
    try:
        base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        db_dir = os.path.join(base, "db")
        zone_path = os.path.join(db_dir, "zone.json")
        tab_path = os.path.join(db_dir, "tab.json")
        if not os.path.exists(zone_path) and os.path.exists(tab_path):
            shutil.copy2(tab_path, zone_path)
            print("Migrated db/tab.json -> db/zone.json")
    except OSError:
        pass


class Zone(Model):
    """Preset layout row (stored in ``db/zone.json``); legacy storage was ``tab.json`` / Tab.

    Optional ``content_kind`` on a row: ``\"presets\"`` (preset tiles only) or ``\"sequences\"``
    (sequence tiles only). Legacy rows without ``content_kind`` are inferred on load.
    """

    def __init__(self):
        if not getattr(Zone, "_migration_checked", False):
            _maybe_migrate_tab_json_to_zone()
            Zone._migration_checked = True
        super().__init__()

    def load(self):
        super().load()
        changed = False
        for zid, doc in list(self.items()):
            if not isinstance(doc, dict):
                continue
            if "group_ids" not in doc:
                doc["group_ids"] = []
                changed = True
            if "preset_group_ids" not in doc or not isinstance(doc.get("preset_group_ids"), dict):
                doc["preset_group_ids"] = {}
                changed = True
            if "sequence_ids" not in doc or not isinstance(doc.get("sequence_ids"), list):
                doc["sequence_ids"] = []
                changed = True
            if not self._normalized_content_kind(doc):
                doc["content_kind"] = self._infer_content_kind(doc)
                changed = True
        if changed:
            self.save()

    @staticmethod
    def _normalized_content_kind(doc):
        if not isinstance(doc, dict):
            return None
        kind = doc.get("content_kind")
        return kind if kind in ("presets", "sequences") else None

    @staticmethod
    def _preset_ids_in_doc(doc):
        if not isinstance(doc, dict):
            return []
        flat = doc.get("presets_flat")
        if isinstance(flat, list):
            return [str(x) for x in flat if x is not None and str(x).strip()]
        presets = doc.get("presets")
        if not isinstance(presets, list) or not presets:
            return []
        if isinstance(presets[0], str):
            return [str(x) for x in presets if x is not None and str(x).strip()]
        if isinstance(presets[0], list):
            out = []
            for row in presets:
                if isinstance(row, list):
                    out.extend(str(x) for x in row if x is not None and str(x).strip())
            return out
        return []

    @classmethod
    def _infer_content_kind(cls, doc):
        kind = cls._normalized_content_kind(doc)
        if kind:
            return kind
        seq_ids = [
            str(x).strip()
            for x in (doc.get("sequence_ids") or [])
            if x is not None and str(x).strip()
        ]
        preset_ids = cls._preset_ids_in_doc(doc)
        if seq_ids and not preset_ids:
            return "sequences"
        return "presets"

    def _enforce_content_kind_invariants(self, doc):
        """Presets-only zones hold no sequences; sequences-only hold no preset tiles."""
        kind = self._normalized_content_kind(doc)
        if kind == "presets":
            doc["sequence_ids"] = []
        elif kind == "sequences":
            doc["presets"] = []
            doc["presets_flat"] = []

    def create(self, name="", names=None, presets=None, group_ids=None, content_kind=None):
        next_id = self.get_next_id()
        gid_list = []
        if isinstance(group_ids, list):
            gid_list = [str(x).strip() for x in group_ids if x is not None and str(x).strip()]
        doc = {
            "name": name,
            "names": names if names else [],
            "group_ids": gid_list,
            "preset_group_ids": {},
            "presets": presets if presets else [],
            "default_preset": None,
            "brightness": 255,
        }
        if content_kind in ("presets", "sequences"):
            doc["content_kind"] = content_kind
        if "sequence_ids" not in doc:
            doc["sequence_ids"] = []
        self._enforce_content_kind_invariants(doc)
        self[next_id] = doc
        self.save()
        return next_id

    def read(self, id):
        id_str = str(id)
        return self.get(id_str, None)

    def update(self, id, data):
        id_str = str(id)
        if id_str not in self:
            return False
        patch = dict(data) if isinstance(data, dict) else {}
        doc = self[id_str]
        locked_kind = self._normalized_content_kind(doc) or self._infer_content_kind(doc)
        if "content_kind" in patch:
            patch["content_kind"] = locked_kind
        self[id_str].update(patch)
        if "content_kind" in patch:
            self._enforce_content_kind_invariants(self[id_str])
        self.save()
        return True

    def delete(self, id):
        id_str = str(id)
        if id_str not in self:
            return False
        self.pop(id_str)
        self.save()
        return True

    def list(self):
        return list(self.keys())
