"""The guardZones spatial layer (Alarms 1 & 2): create / load / style / read.

QGIS owns this GeoPackage so the spatial metadata is guaranteed valid. The layer
carries the per-zone alarm config as attributes:

    zone_name    text
    armed        int  (0/1)        two-level gating: this AND the master switch
    on_entry     int  (0/1)        Alarm 1
    on_exit      int  (0/1)        Alarm 2
    target_mode  text ('all' | 'groups')
    group_ids    text             one or more guard-group ids (many-to-many)

`group_ids` is bound in the attribute form to a multi-select value-relation onto
the target_groups table (loaded from the store file), so the user picks "all
vessels" or one-or-more named guard groups per zone. Reading is tolerant of the
stored format ("{1,3}" or "1,3"). Everything QGIS-touching degrades gracefully:
if a styling/widget step fails the layer is still usable as plain fields.
"""
from .guardian_core import GuardZone, MODE_ALL, MODE_LIST

ZONES_LAYER = "guardZones"


def _q():
    """Import QGIS bits lazily so the module imports under py_compile."""
    from qgis.core import (
        QgsVectorLayer, QgsField, QgsFields, QgsWkbTypes, QgsProject,
        QgsCoordinateReferenceSystem, QgsVectorFileWriter,
    )
    from qgis.PyQt.QtCore import QVariant
    return (QgsVectorLayer, QgsField, QgsFields, QgsWkbTypes, QgsProject,
            QgsCoordinateReferenceSystem, QgsVectorFileWriter, QVariant)


def _build_fields():
    """Build the guardZones attribute schema (zone_name, armed, on_entry,
    on_exit, target_mode, group_ids) as a QgsFields."""
    QgsFields = _q()[2]
    from .qgis_compat import make_field
    f = QgsFields()
    f.append(make_field("zone_name", "str"))
    f.append(make_field("armed", "int"))
    f.append(make_field("on_entry", "int"))
    f.append(make_field("on_exit", "int"))
    f.append(make_field("target_mode", "str"))
    f.append(make_field("group_ids", "str"))
    return f


def _layer_uri(path):
    """OGR data-source URI for the guardZones layer inside a GeoPackage file."""
    return f"{path}|layername={ZONES_LAYER}"


def _valid_zone_layer(path):
    """True if `path` already holds a usable guardZones spatial layer."""
    (QgsVectorLayer, *_rest) = _q()
    try:
        lyr = QgsVectorLayer(_layer_uri(path), ZONES_LAYER, "ogr")
        return lyr.isValid()
    except Exception:
        return False


def _create_zone_gpkg(path):
    """Create (or overwrite) a fresh GeoPackage holding an empty EPSG:4326
    polygon guardZones layer with the standard attribute schema."""
    (QgsVectorLayer, QgsField, QgsFields, QgsWkbTypes, QgsProject,
     QgsCoordinateReferenceSystem, QgsVectorFileWriter, QVariant) = _q()
    crs = QgsCoordinateReferenceSystem("EPSG:4326")
    fields = _build_fields()
    opts = QgsVectorFileWriter.SaveVectorOptions()
    opts.driverName = "GPKG"
    opts.layerName = ZONES_LAYER
    # Fresh file (overwrites any stale same-named file from earlier versions —
    # the relational store now lives in a separate file, so nothing is lost).
    opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
    ctx = QgsProject.instance().transformContext()
    writer = QgsVectorFileWriter.create(
        path, fields, QgsWkbTypes.Polygon, crs, ctx, opts)
    del writer  # flush to disk


def ensure_zone_layer(path):
    """Return a loaded, project-added guardZones layer, creating the GeoPackage
    if needed. Returns the QgsVectorLayer, or None if QGIS isn't available."""
    try:
        (QgsVectorLayer, _f, _fs, _wkb, QgsProject, _crs, _w, _qv) = _q()
    except Exception:
        return None

    # already in the project?
    for lyr in QgsProject.instance().mapLayersByName(ZONES_LAYER):
        if lyr.isValid():
            return lyr

    if not _valid_zone_layer(path):
        _create_zone_gpkg(path)

    layer = QgsVectorLayer(_layer_uri(path), ZONES_LAYER, "ogr")
    if not layer.isValid():
        return None
    QgsProject.instance().addMapLayer(layer)
    try:
        _style_armed(layer)
    except Exception:
        pass
    return layer


def _style_armed(layer):
    """Armed zones get a solid red outline; disarmed ones a faint grey."""
    from qgis.core import (QgsRuleBasedRenderer, QgsFillSymbol)
    base = QgsFillSymbol.createSimple(
        {"color": "255,0,0,40", "outline_color": "200,0,0,255",
         "outline_width": "0.6"})
    root = QgsRuleBasedRenderer.Rule(None)

    armed = QgsRuleBasedRenderer.Rule(base.clone())
    armed.setLabel("Armed")
    armed.setFilterExpression('"armed" = 1')
    root.appendChild(armed)

    disarmed_sym = QgsFillSymbol.createSimple(
        {"color": "120,120,120,25", "outline_color": "120,120,120,200",
         "outline_width": "0.3", "outline_style": "dash"})
    disarmed = QgsRuleBasedRenderer.Rule(disarmed_sym)
    disarmed.setLabel("Disarmed")
    disarmed.setFilterExpression('"armed" IS NULL OR "armed" = 0')
    root.appendChild(disarmed)

    layer.setRenderer(QgsRuleBasedRenderer(root))
    layer.triggerRepaint()


def suppress_native_form(layer):
    """Stop QGIS popping its own attribute form when a zone is digitised — we
    use our own ZoneDialog instead. Best-effort across versions."""
    try:
        from qgis.core import QgsEditFormConfig
        cfg = layer.editFormConfig()
        on = getattr(getattr(QgsEditFormConfig, "FeatureFormSuppress",
                     QgsEditFormConfig), "SuppressOn")
        cfg.setSuppress(on)
        layer.setEditFormConfig(cfg)
    except Exception:
        pass


def configure_form(layer, store):
    """Attribute-form widgets driven directly by the store (no fragile
    cross-file layer dependency). target_mode -> all/groups dropdown; group_ids
    -> a dropdown of the actual guard-group names mapped to their ids. This is
    the interim picker; the full multi-select custom form is the next step."""
    try:
        from qgis.core import QgsEditorWidgetSetup
        idx_mode = layer.fields().indexOf("target_mode")
        layer.setEditorWidgetSetup(idx_mode, QgsEditorWidgetSetup("ValueMap", {
            "map": [{"all vessels": "all"}, {"guard groups": "groups"}]}))
        # build the group picker from the store's current groups
        group_map = [{"(pick a guard group)": ""}]
        try:
            for gid, name in store.list_groups():
                group_map.append({name: str(gid)})
        except Exception:
            pass
        idx_g = layer.fields().indexOf("group_ids")
        layer.setEditorWidgetSetup(idx_g, QgsEditorWidgetSetup("ValueMap",
                                                               {"map": group_map}))
    except Exception:
        pass


# ----------------------------------------------------------------------------
# reading zones -> engine
# ----------------------------------------------------------------------------
def _parse_group_ids(raw):
    """Parse stored group ids into a list of ints, tolerant of formats like
    '{1,3}' or '1,3' (just pulls every run of digits)."""
    if raw is None:
        return []
    import re
    return [int(x) for x in re.findall(r"\d+", str(raw))]


def _ring_from_geometry(geom):
    """Exterior ring of a (multi)polygon as [(lon, lat), ...] in layer CRS."""
    if geom is None or geom.isEmpty():
        return None
    try:
        if geom.isMultipart():
            parts = geom.asMultiPolygon()
            if not parts:
                return None
            exterior = parts[0][0]            # first polygon, exterior ring
        else:
            poly = geom.asPolygon()
            if not poly:
                return None
            exterior = poly[0]
    except Exception:
        return None
    return [(p.x(), p.y()) for p in exterior]


def _as_bool(v):
    """Coerce a stored 0/1 (int, str or None) into a Python bool, tolerantly."""
    try:
        return int(v) != 0
    except (TypeError, ValueError):
        return bool(v)


def read_zones(layer):
    """Read the layer's features into engine GuardZone objects."""
    zones = []
    if layer is None:
        return zones
    try:
        feats = list(layer.getFeatures())
    except Exception:
        return zones
    for f in feats:
        ring = _ring_from_geometry(f.geometry())
        if not ring or len(ring) < 3:
            continue
        try:
            name = f["zone_name"]
        except Exception:
            name = None
        def fld(key, default=None):
            """Read a feature attribute by name, returning `default` if it's
            missing or NULL (closure over the current feature)."""
            try:
                v = f[key]
                return default if v is None else v
            except Exception:
                return default
        mode_raw = (fld("target_mode", "all") or "all")
        mode = MODE_LIST if str(mode_raw).lower().startswith("group") else MODE_ALL
        zones.append(GuardZone(
            zone_id=f.id(),
            name=name or f"zone {f.id()}",
            ring=ring,
            armed=_as_bool(fld("armed", 1)),
            alarm_on_entry=_as_bool(fld("on_entry", 1)),
            alarm_on_exit=_as_bool(fld("on_exit", 0)),
            target_mode=mode,
            target_group_ids=_parse_group_ids(fld("group_ids")),
        ))
    return zones


def load_groups_table(store_path):
    """Load target_groups (from the store gpkg) as a hidden table layer so the
    zone form's value-relation can reference it. Returns the layer or None."""
    try:
        (QgsVectorLayer, *_rest) = _q()
        from qgis.core import QgsProject
    except Exception:
        return None
    name = "guardian_target_groups"
    for lyr in QgsProject.instance().mapLayersByName(name):
        return lyr
    layer = QgsVectorLayer(f"{store_path}|layername=target_groups", name, "ogr")
    if not layer.isValid():
        return None
    QgsProject.instance().addMapLayer(layer, False)   # not in legend
    return layer
