# -*- coding: utf-8 -*-
"""
Coordinate Handlers for Advanced Map Downloader

This module provides functions to parse various coordinate input types
and convert them to QgsRectangle extents for map export.
"""

import math
from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform,
    QgsProject,
    QgsPointXY,
    QgsRectangle,
    QgsGeometry
)
import json


def tile_to_extent(z, x, y):
    """
    Convert XYZ tile coordinates to extent in EPSG:3857.

    :param z: Zoom level
    :param x: X tile coordinate
    :param y: Y tile coordinate
    :returns: QgsRectangle in EPSG:3857
    """
    n = 2.0 ** z

    # Calculate bounds in WGS84
    lon_min = x / n * 360.0 - 180.0
    lon_max = (x + 1) / n * 360.0 - 180.0

    lat_max_rad = math.atan(math.sinh(math.pi * (1 - 2 * y / n)))
    lat_min_rad = math.atan(math.sinh(math.pi * (1 - 2 * (y + 1) / n)))

    lat_max = math.degrees(lat_max_rad)
    lat_min = math.degrees(lat_min_rad)

    # Transform to EPSG:3857
    crs_src = QgsCoordinateReferenceSystem("EPSG:4326")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:3857")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    # Transform corners
    pt_min = transformer.transform(QgsPointXY(lon_min, lat_min))
    pt_max = transformer.transform(QgsPointXY(lon_max, lat_max))

    return QgsRectangle(pt_min.x(), pt_min.y(), pt_max.x(), pt_max.y())


def parse_xyz_tile(z, x, y, gsd=None):
    """
    Parse XYZ tile coordinates and return extent and metadata.

    :param z: Zoom level (int)
    :param x: X tile coordinate (int)
    :param y: Y tile coordinate (int)
    :param gsd: Optional GSD (meters/pixel). If provided, uses this to calculate
                pixel dimensions. If None, uses default 256px tile size.
    :returns: dict with 'extent' (QgsRectangle in EPSG:3857), 'width_px', 'height_px', 'gsd'
    """
    extent = tile_to_extent(z, x, y)

    if gsd is not None and gsd > 0:
        # Use user-specified GSD to calculate pixel dimensions
        width_px = int(extent.width() / gsd)
        height_px = int(extent.height() / gsd)
        result_gsd = gsd
    else:
        # Standard tile size
        tile_size = 256
        width_px = tile_size
        height_px = tile_size
        # Calculate GSD (meters per pixel) at this tile
        result_gsd = extent.width() / tile_size

    return {
        'extent': extent,
        'width_px': width_px,
        'height_px': height_px,
        'gsd': result_gsd,
        'crs': 'EPSG:3857'
    }


def parse_square_grid(lat, lon, size, unit='meters'):
    """
    Parse square grid coordinates (center point + size) and return extent.

    :param lat: Center latitude (decimal degrees)
    :param lon: Center longitude (decimal degrees)
    :param size: Grid size value
    :param unit: Unit of size ('meters', 'kilometers', 'miles', 'nautical_miles')
    :returns: dict with 'extent' (QgsRectangle in EPSG:3857), 'size_m'
    """
    # Convert size to meters
    unit_conversions = {
        'meters': 1.0,
        'kilometers': 1000.0,
        'miles': 1609.344,
        'nautical_miles': 1852.0
    }

    size_m = size * unit_conversions.get(unit, 1.0)
    half_size = size_m / 2

    # Transform center point to EPSG:3857
    crs_src = QgsCoordinateReferenceSystem("EPSG:4326")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:3857")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    center_3857 = transformer.transform(QgsPointXY(lon, lat))

    extent = QgsRectangle(
        center_3857.x() - half_size,
        center_3857.y() - half_size,
        center_3857.x() + half_size,
        center_3857.y() + half_size
    )

    return {
        'extent': extent,
        'size_m': size_m,
        'crs': 'EPSG:3857'
    }


def parse_latlon_pixels(lat, lon, width_px, height_px, gsd):
    """
    Parse lat/lon center with pixel dimensions and GSD.
    This is the original custom_map_downloader system.

    :param lat: Center latitude (decimal degrees)
    :param lon: Center longitude (decimal degrees)
    :param width_px: Output width in pixels
    :param height_px: Output height in pixels
    :param gsd: Ground sampling distance (meters/pixel)
    :returns: dict with 'extent' (QgsRectangle in EPSG:3857), dimensions, gsd
    """
    # Transform center to EPSG:3857
    crs_src = QgsCoordinateReferenceSystem("EPSG:4326")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:3857")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    center_3857 = transformer.transform(QgsPointXY(lon, lat))

    # Calculate extent in meters
    half_width = (width_px * gsd) / 2
    half_height = (height_px * gsd) / 2

    extent = QgsRectangle(
        center_3857.x() - half_width,
        center_3857.y() - half_height,
        center_3857.x() + half_width,
        center_3857.y() + half_height
    )

    return {
        'extent': extent,
        'width_px': width_px,
        'height_px': height_px,
        'gsd': gsd,
        'crs': 'EPSG:3857'
    }


def parse_wkt(wkt_string, gsd=None):
    """
    Parse WKT geometry and return bounding box extent.

    :param wkt_string: Well-Known Text geometry string
    :param gsd: Optional GSD for pixel calculation
    :returns: dict with 'extent', 'geometry', optional dimensions
    """
    geometry = QgsGeometry.fromWkt(wkt_string)

    if geometry.isNull() or geometry.isEmpty():
        raise ValueError("Invalid WKT geometry")

    # Get bounding box (assumes WGS84 input)
    bbox = geometry.boundingBox()

    # Transform to EPSG:3857
    crs_src = QgsCoordinateReferenceSystem("EPSG:4326")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:3857")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    # Transform the bounding box
    pt_min = transformer.transform(
        QgsPointXY(
            bbox.xMinimum(),
            bbox.yMinimum()))
    pt_max = transformer.transform(
        QgsPointXY(
            bbox.xMaximum(),
            bbox.yMaximum()))

    extent = QgsRectangle(pt_min.x(), pt_min.y(), pt_max.x(), pt_max.y())

    result = {
        'extent': extent,
        'geometry': geometry,
        'crs': 'EPSG:3857'
    }

    if gsd:
        result['gsd'] = gsd
        result['width_px'] = int(extent.width() / gsd)
        result['height_px'] = int(extent.height() / gsd)

    return result


def parse_wkb(wkb_hex, gsd=None):
    """
    Parse WKB hex string and return bounding box extent.

    :param wkb_hex: Well-Known Binary as hex string
    :param gsd: Optional GSD for pixel calculation
    :returns: dict with 'extent', 'geometry', optional dimensions
    """
    try:
        wkb_bytes = bytes.fromhex(wkb_hex)
    except ValueError:
        raise ValueError("Invalid WKB hex string")

    geometry = QgsGeometry()
    geometry.fromWkb(wkb_bytes)

    if geometry.isNull() or geometry.isEmpty():
        raise ValueError("Invalid WKB geometry")

    # Get bounding box (assumes WGS84 input)
    bbox = geometry.boundingBox()

    # Transform to EPSG:3857
    crs_src = QgsCoordinateReferenceSystem("EPSG:4326")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:3857")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    pt_min = transformer.transform(
        QgsPointXY(
            bbox.xMinimum(),
            bbox.yMinimum()))
    pt_max = transformer.transform(
        QgsPointXY(
            bbox.xMaximum(),
            bbox.yMaximum()))

    extent = QgsRectangle(pt_min.x(), pt_min.y(), pt_max.x(), pt_max.y())

    result = {
        'extent': extent,
        'geometry': geometry,
        'crs': 'EPSG:3857'
    }

    if gsd:
        result['gsd'] = gsd
        result['width_px'] = int(extent.width() / gsd)
        result['height_px'] = int(extent.height() / gsd)

    return result


def parse_geojson(geojson_string, gsd=None):
    """
    Parse GeoJSON geometry string and return bounding box extent.

    :param geojson_string: GeoJSON geometry string
    :param gsd: Optional GSD for pixel calculation
    :returns: dict with 'extent', 'geometry', optional dimensions
    """
    try:
        geojson_dict = json.loads(geojson_string)
    except json.JSONDecodeError:
        raise ValueError("Invalid JSON format")

    # Convert GeoJSON to WKT
    wkt = geojson_to_wkt(geojson_dict)

    if not wkt:
        raise ValueError("Could not convert GeoJSON to WKT")

    geometry = QgsGeometry.fromWkt(wkt)

    if geometry.isNull() or geometry.isEmpty():
        raise ValueError("Invalid GeoJSON geometry")

    # Get bounding box (assumes WGS84 input)
    bbox = geometry.boundingBox()

    # Transform to EPSG:3857
    crs_src = QgsCoordinateReferenceSystem("EPSG:4326")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:3857")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    pt_min = transformer.transform(
        QgsPointXY(
            bbox.xMinimum(),
            bbox.yMinimum()))
    pt_max = transformer.transform(
        QgsPointXY(
            bbox.xMaximum(),
            bbox.yMaximum()))

    extent = QgsRectangle(pt_min.x(), pt_min.y(), pt_max.x(), pt_max.y())

    result = {
        'extent': extent,
        'geometry': geometry,
        'crs': 'EPSG:3857'
    }

    if gsd:
        result['gsd'] = gsd
        result['width_px'] = int(extent.width() / gsd)
        result['height_px'] = int(extent.height() / gsd)

    return result


def geojson_to_wkt(geojson_dict):
    """
    Convert GeoJSON geometry dict to WKT string.

    :param geojson_dict: GeoJSON geometry as dict
    :returns: WKT string
    """
    geom_type = geojson_dict.get('type', '')
    coords = geojson_dict.get('coordinates', [])

    if geom_type == 'Point':
        return f"POINT ({coords[0]} {coords[1]})"

    elif geom_type == 'LineString':
        points = ', '.join([f"{c[0]} {c[1]}" for c in coords])
        return f"LINESTRING ({points})"

    elif geom_type == 'Polygon':
        rings = []
        for ring in coords:
            points = ', '.join([f"{c[0]} {c[1]}" for c in ring])
            rings.append(f"({points})")
        return f"POLYGON ({', '.join(rings)})"

    elif geom_type == 'MultiPoint':
        points = ', '.join([f"({c[0]} {c[1]})" for c in coords])
        return f"MULTIPOINT ({points})"

    elif geom_type == 'MultiLineString':
        lines = []
        for line in coords:
            points = ', '.join([f"{c[0]} {c[1]}" for c in line])
            lines.append(f"({points})")
        return f"MULTILINESTRING ({', '.join(lines)})"

    elif geom_type == 'MultiPolygon':
        polygons = []
        for polygon in coords:
            rings = []
            for ring in polygon:
                points = ', '.join([f"{c[0]} {c[1]}" for c in ring])
                rings.append(f"({points})")
            polygons.append(f"({', '.join(rings)})")
        return f"MULTIPOLYGON ({', '.join(polygons)})"

    elif geom_type == 'Feature':
        return geojson_to_wkt(geojson_dict.get('geometry', {}))

    elif geom_type == 'FeatureCollection':
        features = geojson_dict.get('features', [])
        if features:
            return geojson_to_wkt(features[0].get('geometry', {}))

    return None


def extent_to_wgs84(extent):
    """
    Convert extent from EPSG:3857 to WGS84.

    :param extent: QgsRectangle in EPSG:3857
    :returns: QgsRectangle in EPSG:4326
    """
    crs_src = QgsCoordinateReferenceSystem("EPSG:3857")
    crs_dest = QgsCoordinateReferenceSystem("EPSG:4326")
    transformer = QgsCoordinateTransform(
        crs_src, crs_dest, QgsProject.instance())

    return transformer.transformBoundingBox(extent)
