# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Geometry Serializers
                                 A QGIS plugin module
 Serialize QgsGeometry objects to various formats for CSV export
                              -------------------
        begin                : 2025-12-21
        copyright            : (C) 2025 by Mirjan Ali Sha
        email                : mastools.help@gmail.com
 ***************************************************************************/
"""

import json
import binascii
from typing import Optional, List
from qgis.core import QgsGeometry, QgsPointXY, QgsWkbTypes


class GeometryFormat:
    """Enum-like class for geometry format types"""
    WKT = 'WKT'
    WKB = 'WKB'
    EWKT = 'EWKT'
    EWKB = 'EWKB'
    GEOJSON = 'GeoJSON'
    KML = 'KML'
    EARTH_ENGINE = 'Earth Engine'
    XY = 'X-Y Coordinates'
    
    @classmethod
    def all_formats(cls) -> List[str]:
        """Return all supported export format names"""
        return [
            cls.WKT, cls.WKB, cls.EWKT, cls.EWKB, 
            cls.GEOJSON, cls.KML, cls.EARTH_ENGINE, cls.XY
        ]
    
    @classmethod
    def get_description(cls, format_type: str) -> str:
        """Get description for each format"""
        descriptions = {
            cls.WKT: 'Well-Known Text - Standard OGC format',
            cls.WKB: 'Well-Known Binary (hex) - Compact binary format',
            cls.EWKT: 'Extended WKT with SRID prefix',
            cls.EWKB: 'Extended WKB with embedded SRID',
            cls.GEOJSON: 'GeoJSON geometry object',
            cls.KML: 'KML coordinates element',
            cls.EARTH_ENGINE: 'Google Earth Engine format',
            cls.XY: 'Separate X (longitude) and Y (latitude) columns'
        }
        return descriptions.get(format_type, '')


class GeometrySerializer:
    """
    Serialize QgsGeometry objects to various format strings.
    
    Supports: WKT, WKB, EWKT, EWKB, GeoJSON, KML, Earth Engine, X-Y
    """
    
    @staticmethod
    def serialize(geom: QgsGeometry, format_type: str, srid: int = 4326) -> Optional[str]:
        """
        Serialize a QgsGeometry object to a format string.
        
        :param geom: QgsGeometry object to serialize
        :param format_type: Output format (from GeometryFormat)
        :param srid: SRID for formats that support it (EWKT, EWKB)
        :return: Serialized geometry string or None if fails
        """
        if not geom or geom.isNull() or geom.isEmpty():
            return None
        
        try:
            if format_type == GeometryFormat.WKT:
                return GeometrySerializer._to_wkt(geom)
            elif format_type == GeometryFormat.WKB:
                return GeometrySerializer._to_wkb(geom)
            elif format_type == GeometryFormat.EWKT:
                return GeometrySerializer._to_ewkt(geom, srid)
            elif format_type == GeometryFormat.EWKB:
                return GeometrySerializer._to_ewkb(geom, srid)
            elif format_type == GeometryFormat.GEOJSON:
                return GeometrySerializer._to_geojson(geom)
            elif format_type == GeometryFormat.KML:
                return GeometrySerializer._to_kml(geom)
            elif format_type == GeometryFormat.EARTH_ENGINE:
                return GeometrySerializer._to_earth_engine(geom)
            elif format_type == GeometryFormat.XY:
                # For X-Y format, return tuple as "x,y" - will be split later
                return GeometrySerializer._to_xy(geom)
            else:
                return GeometrySerializer._to_wkt(geom)
        except Exception:
            return None
    
    @staticmethod
    def _to_wkt(geom: QgsGeometry) -> str:
        """Convert geometry to WKT"""
        return geom.asWkt()
    
    @staticmethod
    def _to_wkb(geom: QgsGeometry) -> str:
        """Convert geometry to WKB hex string"""
        wkb_bytes = geom.asWkb().data()
        return binascii.hexlify(wkb_bytes).decode('ascii').upper()
    
    @staticmethod
    def _to_ewkt(geom: QgsGeometry, srid: int) -> str:
        """Convert geometry to EWKT (Extended WKT with SRID)"""
        wkt = geom.asWkt()
        return f"SRID={srid};{wkt}"
    
    @staticmethod
    def _to_ewkb(geom: QgsGeometry, srid: int) -> str:
        """
        Convert geometry to EWKB (Extended WKB with SRID).
        EWKB encodes the SRID in the geometry type with flag 0x20000000
        """
        # Get WKB bytes
        wkb_bytes = bytearray(geom.asWkb().data())
        
        if len(wkb_bytes) < 5:
            return GeometrySerializer._to_wkb(geom)
        
        # Check byte order (first byte)
        byte_order = wkb_bytes[0]  # 01 = little endian, 00 = big endian
        
        # Modify geometry type to add SRID flag (0x20)
        if byte_order == 1:  # Little endian
            # Type is bytes 1-4 (little endian)
            type_int = int.from_bytes(wkb_bytes[1:5], 'little')
            type_int |= 0x20000000  # Add SRID flag
            wkb_bytes[1:5] = type_int.to_bytes(4, 'little')
            # Insert SRID after type (little endian)
            srid_bytes = srid.to_bytes(4, 'little')
        else:  # Big endian
            type_int = int.from_bytes(wkb_bytes[1:5], 'big')
            type_int |= 0x20000000  # Add SRID flag
            wkb_bytes[1:5] = type_int.to_bytes(4, 'big')
            # Insert SRID after type (big endian)
            srid_bytes = srid.to_bytes(4, 'big')
        
        # Insert SRID bytes after the type (position 5)
        ewkb_bytes = wkb_bytes[:5] + srid_bytes + wkb_bytes[5:]
        
        return binascii.hexlify(ewkb_bytes).decode('ascii').upper()
    
    @staticmethod
    def _to_geojson(geom: QgsGeometry) -> str:
        """Convert geometry to GeoJSON"""
        return geom.asJson()
    
    @staticmethod
    def _to_kml(geom: QgsGeometry) -> str:
        """Convert geometry to KML coordinates element"""
        geom_type = QgsWkbTypes.geometryType(geom.wkbType())
        
        if geom_type == QgsWkbTypes.PointGeometry:
            point = geom.asPoint()
            return f"<Point><coordinates>{point.x()},{point.y()},0</coordinates></Point>"
        
        elif geom_type == QgsWkbTypes.LineGeometry:
            if QgsWkbTypes.isMultiType(geom.wkbType()):
                lines = geom.asMultiPolyline()
                kml_parts = []
                for line in lines:
                    coords = ' '.join([f"{p.x()},{p.y()},0" for p in line])
                    kml_parts.append(f"<LineString><coordinates>{coords}</coordinates></LineString>")
                return f"<MultiGeometry>{''.join(kml_parts)}</MultiGeometry>"
            else:
                line = geom.asPolyline()
                coords = ' '.join([f"{p.x()},{p.y()},0" for p in line])
                return f"<LineString><coordinates>{coords}</coordinates></LineString>"
        
        elif geom_type == QgsWkbTypes.PolygonGeometry:
            if QgsWkbTypes.isMultiType(geom.wkbType()):
                polygons = geom.asMultiPolygon()
                kml_parts = []
                for polygon in polygons:
                    poly_kml = GeometrySerializer._polygon_to_kml(polygon)
                    kml_parts.append(poly_kml)
                return f"<MultiGeometry>{''.join(kml_parts)}</MultiGeometry>"
            else:
                polygon = geom.asPolygon()
                return GeometrySerializer._polygon_to_kml(polygon)
        
        # Fallback to WKT
        return geom.asWkt()
    
    @staticmethod
    def _polygon_to_kml(polygon: list) -> str:
        """Convert a polygon (list of rings) to KML"""
        if not polygon:
            return "<Polygon></Polygon>"
        
        # First ring is outer boundary
        outer_coords = ' '.join([f"{p.x()},{p.y()},0" for p in polygon[0]])
        outer_boundary = f"<outerBoundaryIs><LinearRing><coordinates>{outer_coords}</coordinates></LinearRing></outerBoundaryIs>"
        
        # Inner rings are inner boundaries (holes)
        inner_boundaries = ""
        for ring in polygon[1:]:
            inner_coords = ' '.join([f"{p.x()},{p.y()},0" for p in ring])
            inner_boundaries += f"<innerBoundaryIs><LinearRing><coordinates>{inner_coords}</coordinates></LinearRing></innerBoundaryIs>"
        
        return f"<Polygon>{outer_boundary}{inner_boundaries}</Polygon>"
    
    @staticmethod
    def _to_earth_engine(geom: QgsGeometry) -> str:
        """Convert geometry to Earth Engine Python format"""
        geom_type = QgsWkbTypes.geometryType(geom.wkbType())
        
        if geom_type == QgsWkbTypes.PointGeometry:
            if QgsWkbTypes.isMultiType(geom.wkbType()):
                points = geom.asMultiPoint()
                coords = [[p.x(), p.y()] for p in points]
                return f"ee.Geometry.MultiPoint({json.dumps(coords)})"
            else:
                point = geom.asPoint()
                return f"ee.Geometry.Point([{point.x()}, {point.y()}])"
        
        elif geom_type == QgsWkbTypes.LineGeometry:
            if QgsWkbTypes.isMultiType(geom.wkbType()):
                lines = geom.asMultiPolyline()
                coords = [[[p.x(), p.y()] for p in line] for line in lines]
                return f"ee.Geometry.MultiLineString({json.dumps(coords)})"
            else:
                line = geom.asPolyline()
                coords = [[p.x(), p.y()] for p in line]
                return f"ee.Geometry.LineString({json.dumps(coords)})"
        
        elif geom_type == QgsWkbTypes.PolygonGeometry:
            if QgsWkbTypes.isMultiType(geom.wkbType()):
                polygons = geom.asMultiPolygon()
                coords = [[[[p.x(), p.y()] for p in ring] for ring in poly] for poly in polygons]
                return f"ee.Geometry.MultiPolygon({json.dumps(coords)})"
            else:
                polygon = geom.asPolygon()
                coords = [[[p.x(), p.y()] for p in ring] for ring in polygon]
                return f"ee.Geometry.Polygon({json.dumps(coords)})"
        
        # Fallback to GeoJSON
        return geom.asJson()
    
    @staticmethod
    def _to_xy(geom: QgsGeometry) -> tuple:
        """
        Convert geometry to X,Y coordinates.
        For non-point geometries, returns the centroid.
        Returns tuple (x, y) for later processing.
        """
        geom_type = QgsWkbTypes.geometryType(geom.wkbType())
        
        if geom_type == QgsWkbTypes.PointGeometry:
            if QgsWkbTypes.isMultiType(geom.wkbType()):
                # For multipoint, use first point
                points = geom.asMultiPoint()
                if points:
                    return (points[0].x(), points[0].y())
            else:
                point = geom.asPoint()
                return (point.x(), point.y())
        
        # For non-point geometries, use centroid
        centroid = geom.centroid()
        if centroid and not centroid.isNull():
            point = centroid.asPoint()
            return (point.x(), point.y())
        
        return (None, None)
    
    @staticmethod
    def requires_xy_columns(format_type: str) -> bool:
        """Check if format requires separate X and Y columns"""
        return format_type == GeometryFormat.XY
