import math
from PIL import Image
import urllib.request
from io import BytesIO
import time


class GoogleMapDownloader:
    def __init__(self, zoom=12, layer='raw_satellite'):
        self._zoom = zoom
        self.layer_map = {
            'roadmap': 'm',
            'terrain': 'p',
            'satellite': 's',
            'hybrid': 'y',
            'raw_satellite': 's'
        }
        self._layer = self.layer_map.get(layer, 's')
        self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else ''

    def latlng_to_tile(self, lat, lng):
        """Convierte coordenadas a tiles X/Y con precisión decimal"""
        tile_size = 256
        numTiles = 1 << self._zoom

        point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size
        sin_y = math.sin(lat * (math.pi / 180.0))
        point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) *
                   -(tile_size / (2 * math.pi))) * numTiles / tile_size

        return point_x, point_y

    def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng):
        """Genera la imagen para un área rectangular definida por coordenadas"""
        # Convertir coordenadas a tiles con precisión decimal
        sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng)
        ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng)

        # Asegurar que las coordenadas estén en el orden correcto
        min_x = min(sw_x, ne_x)
        max_x = max(sw_x, ne_x)
        min_y = min(sw_y, ne_y)
        max_y = max(sw_y, ne_y)

        # Calcular los tiles mínimos y máximos necesarios
        min_tile_x = math.floor(min_x)
        max_tile_x = math.ceil(max_x)
        min_tile_y = math.floor(min_y)
        max_tile_y = math.ceil(max_y)

        # Calcular dimensiones en tiles
        tile_width = int(max_tile_x - min_tile_x) + 1
        tile_height = int(max_tile_y - min_tile_y) + 1

        # Crear imagen temporal para todos los tiles necesarios
        full_img = Image.new('RGB', (tile_width * 256, tile_height * 256))
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
        servers = ['mt0', 'mt1', 'mt2', 'mt3']

        for x in range(min_tile_x, max_tile_x + 1):
            for y in range(min_tile_y, max_tile_y + 1):
                server = servers[(x + y) % len(servers)]
                base_url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}"
                url = f"{base_url}&{self._style}" if self._style else base_url

                try:
                    req = urllib.request.Request(url, headers=headers)
                    with urllib.request.urlopen(req) as response:
                        tile_data = response.read()

                    img = Image.open(BytesIO(tile_data))
                    pos_x = (x - min_tile_x) * 256
                    pos_y = (y - min_tile_y) * 256
                    full_img.paste(img, (pos_x, pos_y))
                    #print(f"✅ Tile ({x}, {y}) descargado")

                except Exception as e:
                    #print(f"❌ Error en tile ({x},{y}): {str(e)}")
                    error_tile = Image.new('RGB', (256, 256), (255, 0, 0))
                    full_img.paste(error_tile, (pos_x, pos_y))

                time.sleep(0.05)

        # Calcular desplazamientos para recorte final
        left_offset = int((min_x - min_tile_x) * 256)
        right_offset = int((max_tile_x - max_x) * 256)
        top_offset = int((min_y - min_tile_y) * 256)
        bottom_offset = int((max_tile_y - max_y) * 256)

        # Calcular coordenadas de recorte
        left = left_offset
        top = top_offset
        right = full_img.width - right_offset
        bottom = full_img.height - bottom_offset

        # Asegurar que las coordenadas sean válidas
        if right < left:
            right = left + 1
        if bottom < top:
            bottom = top + 1
        # Recortar la imagen al área exacta solicitada
        result = full_img.crop((
            left,
            top,
            right,
            bottom
        ))

        return full_img


class GoogleMapDownloader_1:
    def __init__(self, zoom=12, layer='hybrid'):
        """
        Args:
            zoom: Zoom level (0-23)
            layer: Map type (roadmap, terrain, satellite, hybrid)
        """
        self._zoom = zoom
        self.layer_map = {
            'roadmap': 'm',
            'terrain': 'p',
            'satellite': 's',
            'hybrid': 'y',
            'raw_satellite': 's'  # Capa especial sin etiquetas
        }
        self._layer = self.layer_map.get(layer, 's')
        self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else ''

    def latlng_to_tile(self, lat, lng):
        """Convierte coordenadas a tiles X/Y"""
        tile_size = 256
        numTiles = 1 << self._zoom

        # Cálculo para coordenada X
        point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size

        # Cálculo para coordenada Y
        sin_y = math.sin(lat * (math.pi / 180.0))
        point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) *
                   -(tile_size / (2 * math.pi))) * numTiles / tile_size

        return int(point_x), int(point_y)

    def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng):
        """
        Genera la imagen para un área rectangular definida por:
        - sw_lat, sw_lng: Esquina suroeste (latitud, longitud)
        - ne_lat, ne_lng: Esquina noreste (latitud, longitud)
        """
        # Convertir coordenadas a tiles
        sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng)
        ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng)

        # Determinar rango de tiles
        min_x = min(sw_x, ne_x)
        max_x = max(sw_x, ne_x)
        min_y = min(sw_y, ne_y)
        max_y = max(sw_y, ne_y)

        # Calcular dimensiones en tiles
        tile_width = max_x - min_x + 1
        tile_height = max_y - min_y + 1

        # Crear imagen final
        result = Image.new('RGB', (256 * tile_width, 256 * tile_height))
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
        servers = ['mt0', 'mt1', 'mt2', 'mt3']

        print(f"Descargando {tile_width}x{tile_height} tiles ({tile_width * tile_height} total)")

        for x in range(min_x, max_x + 1):
            for y in range(min_y, max_y + 1):
                # Seleccionar servidor rotatorio
                server = servers[(x + y) % len(servers)]
                # Construir URL con parámetro para quitar etiquetas si es necesario
                url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}"
                if self._style:
                    url = f"{url}&{self._style}"

                print("Descargando tile:", url)
                try:
                    # Descargar tile
                    req = urllib.request.Request(url, headers=headers)
                    with urllib.request.urlopen(req) as response:
                        tile_data = response.read()

                    # Procesar en memoria
                    img = Image.open(BytesIO(tile_data))
                    pos_x = (x - min_x) * 256
                    pos_y = (y - min_y) * 256
                    result.paste(img, (pos_x, pos_y))

                    print(f"✅ Tile ({x}, {y}) descargado")

                except Exception as e:
                    print(f"❌ Error en tile ({x},{y}): {str(e)}")
                    # Crear tile de error (rojo)
                    error_tile = Image.new('RGB', (256, 256), (255, 0, 0))
                    pos_x = (x - min_x) * 256
                    pos_y = (y - min_y) * 256
                    result.paste(error_tile, (pos_x, pos_y))

                # Pausa para evitar bloqueos
                time.sleep(0.05)

        return result