"""Merge multiple SHP files into a single KML/KMZ with per-geometry-type styling.

Each SHP becomes a <Folder> inside a single <Document>.
Point/Line/Polygon layers each get independent style, label, and popup configs.
"""

import os
import zipfile
from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform, QgsProject, QgsVectorLayer, QgsWkbTypes
)
from .color_utils import hex_to_kml_color
from .html_template import HtmlTemplateBuilder


class MergeKmlBuilder:
    """Build a single KML/KMZ from multiple SHP files."""

    # Geometry type keys used in configs dict
    GEOM_POINT = 'point'
    GEOM_LINE = 'line'
    GEOM_POLYGON = 'polygon'

    def __init__(self, configs):
        """Initialize with per-geometry-type configs.

        Args:
            configs: dict with keys 'point', 'line', 'polygon'.
                     Each value is a config dict identical to the one
                     used by KmlBuilder (name_fields, description_fields,
                     polygon_style/line_style/point_style, header,
                     row_highlights).
        """
        self.configs = configs
        self._html_builders = {}
        for gtype in (self.GEOM_POINT, self.GEOM_LINE, self.GEOM_POLYGON):
            cfg = configs.get(gtype, {})
            if cfg:
                self._html_builders[gtype] = HtmlTemplateBuilder(cfg)

    def build(self, shp_paths, output_path, output_format='kml'):
        """Build merged KML/KMZ from a list of SHP file paths.

        Args:
            shp_paths: list of absolute paths to .shp files
            output_path: path for the output .kml or .kmz
            output_format: 'kml' or 'kmz'

        Returns:
            (success: bool, message: str)
        """
        try:
            target_crs = QgsCoordinateReferenceSystem('EPSG:4326')
            kml_lines = [
                '<?xml version="1.0" encoding="UTF-8"?>',
                '<kml xmlns="http://www.opengis.net/kml/2.2">',
                '<Document>',
                '<name>Merged SHP Layers</name>'
            ]

            # Build styles for all three geometry types
            kml_lines.extend(self._build_all_styles())

            loaded_count = 0
            errors = []

            for shp_path in shp_paths:
                basename = os.path.splitext(os.path.basename(shp_path))[0]
                layer = QgsVectorLayer(shp_path, basename, 'ogr')
                if not layer.isValid():
                    errors.append(f"Cannot load: {basename}")
                    continue

                gtype = self._detect_geom_type(layer)
                if gtype is None:
                    errors.append(f"Unknown geometry: {basename}")
                    continue

                transform = None
                if layer.crs() != target_crs:
                    transform = QgsCoordinateTransform(
                        layer.crs(), target_crs, QgsProject.instance())

                cfg = self.configs.get(gtype, {})
                name_cfg = cfg.get('name_fields', {})

                kml_lines.append('<Folder>')
                kml_lines.append(f'<name>{self._esc(basename)}</name>')

                for feat in layer.getFeatures():
                    pm = self._build_placemark(
                        feat, gtype, name_cfg, transform)
                    if pm:
                        kml_lines.extend(pm)

                kml_lines.append('</Folder>')
                loaded_count += 1

            kml_lines.extend(['</Document>', '</kml>'])
            kml_content = '\n'.join(kml_lines)

            if loaded_count == 0:
                err_detail = '\n'.join(errors) if errors else 'No valid layers'
                return False, f"No layers exported.\n{err_detail}"

            if output_format == 'kmz':
                ok, msg = self._write_kmz(kml_content, output_path)
            else:
                ok, msg = self._write_kml(kml_content, output_path)

            if ok and errors:
                msg += "\n\nWarnings:\n" + '\n'.join(errors)

            return ok, msg

        except Exception as e:
            return False, f"Error: {str(e)}"

    def _detect_geom_type(self, layer):
        """Detect geometry type of a vector layer."""
        gt = layer.geometryType()
        if gt == QgsWkbTypes.PointGeometry:
            return self.GEOM_POINT
        elif gt == QgsWkbTypes.LineGeometry:
            return self.GEOM_LINE
        elif gt == QgsWkbTypes.PolygonGeometry:
            return self.GEOM_POLYGON
        return None

    def _build_all_styles(self):
        """Build KML <Style> elements for all geometry types."""
        lines = []

        # Point style
        pt_cfg = self.configs.get(self.GEOM_POINT, {})
        pt_style = pt_cfg.get('point_style', {})
        pt_name = pt_cfg.get('name_fields', {})
        pt_color = hex_to_kml_color(
            pt_style.get('icon_color', '#FF4444'), 100)
        pt_scale = pt_style.get('icon_scale', 1.0)
        pt_name_color = hex_to_kml_color(
            pt_name.get('font_color', '#000000'), 100)
        pt_name_size = pt_name.get('font_size', 12)
        pt_label_scale = round(max(0.5, pt_name_size / 12.0), 1)

        lines.append('<Style id="style_point">')
        lines.append(
            f'<IconStyle><color>{pt_color}</color>'
            f'<scale>{pt_scale}</scale></IconStyle>')
        lines.append(
            f'<LabelStyle><color>{pt_name_color}</color>'
            f'<scale>{pt_label_scale}</scale></LabelStyle>')
        lines.append('</Style>')

        # Line style
        ln_cfg = self.configs.get(self.GEOM_LINE, {})
        ln_style = ln_cfg.get('line_style', {})
        ln_name = ln_cfg.get('name_fields', {})
        ln_color = hex_to_kml_color(
            ln_style.get('line_color', '#0000FF'), 100)
        ln_width = ln_style.get('line_width', 2)
        ln_name_color = hex_to_kml_color(
            ln_name.get('font_color', '#000000'), 100)
        ln_name_size = ln_name.get('font_size', 12)
        ln_label_scale = round(max(0.5, ln_name_size / 12.0), 1)

        lines.append('<Style id="style_line">')
        lines.append('<IconStyle><scale>0</scale></IconStyle>')
        lines.append(
            f'<LabelStyle><color>{ln_name_color}</color>'
            f'<scale>{ln_label_scale}</scale></LabelStyle>')
        lines.append(
            f'<LineStyle><color>{ln_color}</color>'
            f'<width>{ln_width}</width></LineStyle>')
        lines.append('</Style>')

        # Polygon style
        pg_cfg = self.configs.get(self.GEOM_POLYGON, {})
        pg_style = pg_cfg.get('polygon_style', {})
        pg_name = pg_cfg.get('name_fields', {})
        pg_border = hex_to_kml_color(
            pg_style.get('border_color', '#FF0000'), 100)
        pg_fill = hex_to_kml_color(
            pg_style.get('fill_color', '#00FF00'),
            pg_style.get('fill_opacity', 50))
        pg_bw = pg_style.get('border_width', 2)
        pg_name_color = hex_to_kml_color(
            pg_name.get('font_color', '#000000'), 100)
        pg_name_size = pg_name.get('font_size', 12)
        pg_label_scale = round(max(0.5, pg_name_size / 12.0), 1)

        lines.append('<Style id="style_polygon">')
        lines.append('<IconStyle><scale>0</scale></IconStyle>')
        lines.append(
            f'<LabelStyle><color>{pg_name_color}</color>'
            f'<scale>{pg_label_scale}</scale></LabelStyle>')
        lines.append(
            f'<LineStyle><color>{pg_border}</color>'
            f'<width>{pg_bw}</width></LineStyle>')
        lines.append(
            f'<PolyStyle><color>{pg_fill}</color></PolyStyle>')
        lines.append('</Style>')

        return lines

    def _build_placemark(self, feat, gtype, name_cfg, transform):
        """Build placemark XML lines for a feature."""
        geom = feat.geometry()
        if geom is None or geom.isEmpty():
            return []

        if transform:
            geom.transform(transform)

        name = self._build_name(feat, name_cfg)
        fdata = {f.name(): feat[f.name()] for f in feat.fields()}

        html_builder = self._html_builders.get(gtype)
        desc = html_builder.build(fdata) if html_builder else ''

        style_id = f'style_{gtype}'
        lines = []

        if gtype == self.GEOM_POLYGON:
            # Placemark 1: Polygon shape without label
            lines.extend([
                '<Placemark>', '<name></name>',
                f'<styleUrl>#{style_id}</styleUrl>',
                f'<description><![CDATA[{desc}]]></description>'
            ])
            lines.extend(self._poly_kml(geom))
            lines.append('</Placemark>')

            # Placemark 2: Centroid label point with Region
            centroid = (geom.pointOnSurface()
                        if hasattr(geom, 'pointOnSurface')
                        else geom.centroid())
            if centroid and centroid.asPoint():
                pt = centroid.asPoint()
                north = pt.y() + 0.0002
                south = pt.y() - 0.0002
                east = pt.x() + 0.0002
                west = pt.x() - 0.0002
                lines.extend([
                    '<Placemark>',
                    f'<name>{self._esc(name)}</name>',
                    f'<styleUrl>#{style_id}</styleUrl>',
                    f'<description><![CDATA[{desc}]]></description>',
                    '<Region><LatLonAltBox>',
                    f'<north>{north}</north><south>{south}</south>',
                    f'<east>{east}</east><west>{west}</west>',
                    '</LatLonAltBox><Lod>'
                    '<minLodPixels>16</minLodPixels>'
                    '<maxLodPixels>-1</maxLodPixels>'
                    '</Lod></Region>',
                    f'<Point><coordinates>'
                    f'{pt.x()},{pt.y()},0</coordinates></Point>',
                    '</Placemark>'
                ])

        elif gtype == self.GEOM_LINE:
            lines.extend([
                '<Placemark>',
                f'<name>{self._esc(name)}</name>',
                f'<styleUrl>#{style_id}</styleUrl>',
                f'<description><![CDATA[{desc}]]></description>'
            ])
            lines.extend(self._line_kml(geom))
            lines.append('</Placemark>')

        elif gtype == self.GEOM_POINT:
            lines.extend([
                '<Placemark>',
                f'<name>{self._esc(name)}</name>',
                f'<styleUrl>#{style_id}</styleUrl>',
                f'<description><![CDATA[{desc}]]></description>'
            ])
            lines.extend(self._pt_kml(geom))
            lines.append('</Placemark>')

        return lines

    def _build_name(self, feat, cfg):
        """Build display name from field config."""
        f1 = cfg.get('field1', '')
        f2 = cfg.get('field2', '')
        f1_on = cfg.get('field1_enabled', True)
        f2_on = cfg.get('field2_enabled', True)
        sep = cfg.get('separator', ' - ')
        fnames = [f.name() for f in feat.fields()]
        v1 = (str(feat[f1])
              if f1_on and f1 in fnames and feat[f1] is not None
              else '')
        v2 = (str(feat[f2])
              if f2_on and f2 in fnames and feat[f2] is not None
              else '')
        if v1 and v2:
            return f"{v1}{sep}{v2}"
        return v1 or v2 or 'Unnamed'

    # ── Geometry writers ──────────────────────────────

    def _poly_kml(self, geom):
        lines = []
        if geom.isMultipart():
            lines.append('<MultiGeometry>')
            for part in geom.asMultiPolygon():
                lines.extend(self._single_poly(part))
            lines.append('</MultiGeometry>')
        else:
            p = geom.asPolygon()
            if p:
                lines.append('<MultiGeometry>')
                lines.extend(self._single_poly(p))
                lines.append('</MultiGeometry>')
        return lines

    def _single_poly(self, rings):
        lines = [
            '<Polygon>',
            '<outerBoundaryIs><LinearRing><coordinates>'
        ]
        lines.append(' '.join(f'{p.x()},{p.y()},0' for p in rings[0]))
        lines.append('</coordinates></LinearRing></outerBoundaryIs>')
        for i in range(1, len(rings)):
            lines.append(
                '<innerBoundaryIs><LinearRing><coordinates>')
            lines.append(
                ' '.join(f'{p.x()},{p.y()},0' for p in rings[i]))
            lines.append(
                '</coordinates></LinearRing></innerBoundaryIs>')
        lines.append('</Polygon>')
        return lines

    def _line_kml(self, geom):
        lines = []
        if geom.isMultipart():
            lines.append('<MultiGeometry>')
            for part in geom.asMultiPolyline():
                lines.append('<LineString><coordinates>')
                lines.append(
                    ' '.join(f'{p.x()},{p.y()},0' for p in part))
                lines.append('</coordinates></LineString>')
            lines.append('</MultiGeometry>')
        else:
            pl = geom.asPolyline()
            if pl:
                lines.append('<LineString><coordinates>')
                lines.append(
                    ' '.join(f'{p.x()},{p.y()},0' for p in pl))
                lines.append('</coordinates></LineString>')
        return lines

    def _pt_kml(self, geom):
        lines = []
        if geom.isMultipart():
            lines.append('<MultiGeometry>')
            for pt in geom.asMultiPoint():
                lines.append(
                    f'<Point><coordinates>'
                    f'{pt.x()},{pt.y()},0</coordinates></Point>')
            lines.append('</MultiGeometry>')
        else:
            pt = geom.asPoint()
            lines.append(
                f'<Point><coordinates>'
                f'{pt.x()},{pt.y()},0</coordinates></Point>')
        return lines

    # ── I/O helpers ───────────────────────────────────

    @staticmethod
    def _write_kml(content, path):
        try:
            with open(path, 'w', encoding='utf-8') as f:
                f.write(content)
            return True, f"KML saved: {path}"
        except Exception as e:
            return False, str(e)

    @staticmethod
    def _write_kmz(kml_content, path):
        try:
            with zipfile.ZipFile(path, 'w', zipfile.ZIP_DEFLATED) as zf:
                zf.writestr('doc.kml', kml_content)
            return True, f"KMZ saved: {path}"
        except Exception as e:
            return False, str(e)

    @staticmethod
    def _esc(t):
        if t is None:
            return ''
        return str(t).replace('&', '&amp;').replace(
            '<', '&lt;').replace('>', '&gt;')
