from qgis.core import QgsFeature, QgsGeometry, QgsWkbTypes


def _safe_text(value):
    if value is None:
        return ""
    return str(value).strip()


def _norm_text(value):
    return _safe_text(value).upper()


def _is_system_field(name):
    if name is None:
        return False
    return str(name).strip().lower() in ("fid", "ogc_fid")


def intersection_area(g1, g2):
    try:
        if g1 is None or g2 is None or g1.isEmpty() or g2.isEmpty():
            return 0.0
        inter = g1.intersection(g2)
        if inter.isEmpty():
            return 0.0
        return float(inter.area())
    except Exception:
        return 0.0


def polygon_parts(geom):
    if geom is None or geom.isEmpty():
        return []

    if QgsWkbTypes.geometryType(geom.wkbType()) != QgsWkbTypes.PolygonGeometry:
        return []

    try:
        if QgsWkbTypes.isMultiType(geom.wkbType()):
            return [QgsGeometry.fromPolygonXY(p) for p in geom.asMultiPolygon() if p]
        else:
            return [geom]
    except Exception:
        return []


def find_base_feature(layer, geom, exclude=None):
    best = None
    best_area = 0.0

    if layer is None or geom is None or geom.isEmpty():
        return None

    for feat in layer.getFeatures():
        if exclude is not None and feat.id() == exclude:
            continue

        area = intersection_area(geom, feat.geometry())
        if area > best_area:
            best_area = area
            best = feat

    return best


def copy_attributes(src_feat):
    if not src_feat:
        return {}

    attrs = {}
    for f in src_feat.fields():
        name = f.name()
        if _is_system_field(name):
            continue
        attrs[name] = src_feat[name]

    if "AREA" in attrs:
        attrs["AREA"] = None

    return attrs


def apply_attributes(layer, fid, attrs):
    if layer is None or not layer.isValid():
        return False

    ok_all = True
    for name, value in attrs.items():
        if _is_system_field(name):
            continue

        idx = layer.fields().indexOf(name)
        if idx != -1:
            ok = layer.changeAttributeValue(fid, idx, value)
            if not ok:
                ok_all = False
    return ok_all


def _apply_named_attributes_to_feature(feature, attrs):
    if feature is None or attrs is None:
        return

    for f in feature.fields():
        name = f.name()
        if _is_system_field(name):
            continue
        if name in attrs:
            try:
                feature.setAttribute(name, attrs[name])
            except Exception:
                pass


def initial_values_for_new_parcel(layer, feature):
    base = find_base_feature(layer, feature.geometry(), exclude=feature.id())
    attrs = copy_attributes(base)
    return attrs


def find_containing_parcel(parcels_layer, geom):
    if parcels_layer is None or geom is None or geom.isEmpty():
        return None

    for feat in parcels_layer.getFeatures():
        try:
            pgeom = feat.geometry()
            if pgeom and not pgeom.isEmpty():
                if geom.within(pgeom) or geom.coveredBy(pgeom):
                    return feat
        except Exception:
            pass
    return None


def building_attrs_from_parcel(parcel_feat):
    """
    KULT nuk merret kurrë nga parcela.
    """
    if not parcel_feat:
        return {}

    attrs = {}
    names = parcel_feat.fields().names()

    if "PARCNO" in names:
        attrs["PARCNO"] = parcel_feat["PARCNO"]

    if "CADMUN" in names:
        attrs["CADMUN"] = parcel_feat["CADMUN"]

    if "PLIST" in names:
        attrs["PLIST"] = parcel_feat["PLIST"]

    if "MESTO" in names:
        attrs["MESTO"] = parcel_feat["MESTO"]

    attrs["AREA"] = None
    return attrs


def initial_values_for_new_building(buildings_layer, parcels_layer, feature):
    geom = feature.geometry()
    if geom is None or geom.isEmpty():
        return {}

    base_building = find_base_feature(buildings_layer, geom, exclude=feature.id())

    if base_building:
        attrs = copy_attributes(base_building)
        attrs["KULT"] = base_building["KULT"] if "KULT" in base_building.fields().names() else None
        return attrs

    parcel = find_containing_parcel(parcels_layer, geom)
    if parcel:
        attrs = building_attrs_from_parcel(parcel)
        attrs["KULT"] = None
        return attrs

    return {}


def enforce_no_overlap_same_layer(layer, fid):
    """
    Version i sigurt për session GPKG:
    - poligoni i ri mbetet
    - poligoni i vjetër pritet
    - feature të reja shtesë krijohen pa kopjuar fid/FID
    """
    if layer is None or not layer.isValid():
        return

    new_feat = layer.getFeature(fid)
    if not new_feat.isValid():
        return

    new_geom = new_feat.geometry()
    if new_geom is None or new_geom.isEmpty():
        return

    to_delete = []
    to_change = []
    to_add = []

    for old_feat in layer.getFeatures():
        if old_feat.id() == fid:
            continue

        old_geom = old_feat.geometry()
        if old_geom is None or old_geom.isEmpty():
            continue

        try:
            if not new_geom.intersects(old_geom):
                continue
        except Exception:
            continue

        diff = old_geom.difference(new_geom)

        if diff.isEmpty():
            to_delete.append(old_feat.id())
            continue

        parts = polygon_parts(diff)
        if not parts:
            to_delete.append(old_feat.id())
            continue

        to_change.append((old_feat.id(), parts[0]))

        if len(parts) > 1:
            old_attrs = copy_attributes(old_feat)
            for extra_geom in parts[1:]:
                nf = QgsFeature(layer.fields())
                nf.setGeometry(extra_geom)
                _apply_named_attributes_to_feature(nf, old_attrs)
                to_add.append(nf)

    for old_fid in to_delete:
        layer.deleteFeature(old_fid)

    for old_fid, geom in to_change:
        layer.changeGeometry(old_fid, geom)

    if to_add:
        layer.addFeatures(to_add)


def sync_buildings_parcno(parcels_layer, buildings_layer):
    """
    Çdo objekt duhet të ketë PARCNO identik me parcelën që e përmban.
    """
    if parcels_layer is None or buildings_layer is None:
        return

    parcno_idx = buildings_layer.fields().indexOf("PARCNO")
    if parcno_idx == -1:
        return

    for b in buildings_layer.getFeatures():
        geom = b.geometry()
        if geom is None or geom.isEmpty():
            continue

        containing_parcel = find_containing_parcel(parcels_layer, geom)
        if containing_parcel is None:
            continue

        parcel_parcno = containing_parcel["PARCNO"]
        current_parcno = b["PARCNO"]

        if str(current_parcno) != str(parcel_parcno):
            buildings_layer.changeAttributeValue(
                b.id(),
                parcno_idx,
                parcel_parcno
            )