# -*- coding: utf-8 -*-
import os
import urllib.request
import urllib.parse
import urllib.error
import ssl
import time
import tempfile
import shutil
import json
from qgis.core import (
    QgsProcessing,
    QgsProcessingAlgorithm,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterString,
    QgsProcessingParameterFolderDestination,
    QgsProcessingParameterEnum,
    QgsFeatureRequest,
    QgsGeometry,
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform,
    QgsProject
)
from qgis import processing
from .license_guard import SupabaseGuard


class DownloadPetaCetakRBI(QgsProcessingAlgorithm):
    MODE = 'MODE'
    SKALA = 'SKALA'
    AOI = 'AOI'
    INDEX_GRID = 'INDEX_GRID'
    TOKEN = 'TOKEN'  # nosec B105
    OUTPUT_FOLDER = 'OUTPUT_FOLDER'

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterEnum(
            self.MODE, 'Mode Pengambilan Grid',
            options=['Online (Otomatis dari Server)', 'Offline (Intersect dengan Layer Index)'],
            defaultValue=0
        ))

        self.addParameter(QgsProcessingParameterEnum(
            self.SKALA, 'Skala Peta Cetak RBI',
            options=['25K', '50K', '250K'],
            defaultValue=0
        ))

        self.addParameter(QgsProcessingParameterFeatureSource(
            self.AOI, 'Area of Interest (AOI)', [QgsProcessing.SourceType.TypeVectorPolygon]
        ))

        self.addParameter(QgsProcessingParameterFeatureSource(
            self.INDEX_GRID, 'Layer Index Grid Lokal (Hanya untuk Mode Offline)',
            [QgsProcessing.SourceType.TypeVectorPolygon], optional=True
        ))

        self.addParameter(QgsProcessingParameterString(
            self.TOKEN, 'API Token'
        ))
        
        self.addParameter(QgsProcessingParameterString(
            'FILTER_NOMOR', 'Filter Nomor Peta (Pisahkan dengan koma, kosongkan untuk semua)', optional=True
        ))

        self.addParameter(QgsProcessingParameterFolderDestination(
            self.OUTPUT_FOLDER, 'Output Folder'
        ))

    def processAlgorithm(self, parameters, context, feedback):
        # 1. License Check
        feedback.pushInfo("Checking license...")
        is_valid, msg, info = SupabaseGuard.check_license("BIG Downloader QGIS", "free")
        feedback.pushInfo(msg)
        if not is_valid:
            raise Exception("License check failed.")

        mode_idx = self.parameterAsEnum(parameters, self.MODE, context)
        skala_idx = self.parameterAsEnum(parameters, self.SKALA, context)
        
        skala_map = {0: '25K', 1: '50K', 2: '250K'}
        layer_map = {0: 1, 1: 2, 2: 3}
        
        skala = skala_map[skala_idx]
        layer_id = layer_map[skala_idx]
        
        aoi_source = self.parameterAsSource(parameters, self.AOI, context)
        token = self.parameterAsString(parameters, self.TOKEN, context)
        filter_str = self.parameterAsString(parameters, 'FILTER_NOMOR', context)
        out_folder = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context)
        
        nomor_petas = set()
        
        if mode_idx == 0:  # Online
            # Iterate features to get strict extent
            from qgis.core import QgsRectangle
            exact_extent = QgsRectangle()
            for f in aoi_source.getFeatures():
                if f.hasGeometry():
                    if exact_extent.isEmpty():
                        exact_extent = f.geometry().boundingBox()
                    else:
                        exact_extent.combineExtentWith(f.geometry().boundingBox())
            
            if exact_extent.isEmpty():
                exact_extent = aoi_source.sourceExtent()
                
            crs_src = aoi_source.sourceCrs()
            crs_dest = QgsCoordinateReferenceSystem("EPSG:4326")
            transform = QgsCoordinateTransform(crs_src, crs_dest, context.transformContext())
            
            extent_wgs84 = transform.transformBoundingBox(exact_extent)
            xmin, ymin, xmax, ymax = extent_wgs84.xMinimum(), extent_wgs84.yMinimum(), extent_wgs84.xMaximum(), extent_wgs84.yMaximum()
            
            geom_str = f"{xmin},{ymin},{xmax},{ymax}"
            feedback.pushInfo(f"Mencari grid RBI {skala} di MapServer BIG untuk extent: {geom_str}")
            
            url = f"https://geoservices.big.go.id/gis/rest/services/PPIG/IDX_Download/MapServer/{layer_id}/query"
            params = {
                'geometry': geom_str,
                'geometryType': 'esriGeometryEnvelope',
                'spatialRel': 'esriSpatialRelIntersects',
                'outFields': 'NOMOR_PETA',
                'f': 'json'
            }
            
            data = urllib.parse.urlencode(params).encode("utf-8")
            req = urllib.request.Request(url, data=data)
            try:
                with urllib.request.urlopen(req, timeout=30) as response:  # nosec B310
                    res_text = response.read().decode('utf-8')
                    res_json = json.loads(res_text)
                    if 'features' in res_json:
                        for feat in res_json['features']:
                            nomor = feat.get('attributes', {}).get('NOMOR_PETA')
                            if nomor:
                                nomor_petas.add(str(nomor))
                    else:
                        feedback.pushWarning("Gagal memparsing respons dari MapServer.")
            except Exception as e:
                raise Exception(f"Gagal menghubungi MapServer BIG: {str(e)}")
        
        else:  # Offline
            feedback.pushInfo(f"Menggunakan Index Grid lokal untuk RBI {skala}...")
            grid_source = self.parameterAsSource(parameters, self.INDEX_GRID, context)
            if not grid_source:
                raise Exception("Layer Index Grid lokal harus diisi jika menggunakan Mode Offline.")
                
            # Intersect
            # Ambil semua geometri AOI
            aoi_geoms = []
            for f in aoi_source.getFeatures():
                if f.hasGeometry():
                    aoi_geoms.append(f.geometry())
                    
            if not aoi_geoms:
                raise Exception("AOI tidak memiliki geometri.")
                
            # Cek field NOMOR_PETA
            fields = grid_source.fields()
            target_idx = -1
            for i, field in enumerate(fields):
                if field.name().upper() == "NOMOR_PETA":
                    target_idx = i
                    break
            
            if target_idx == -1:
                # Coba cari ID
                for i, field in enumerate(fields):
                    if field.name().upper() == "ID":
                        target_idx = i
                        break
                        
            if target_idx == -1:
                raise Exception("Tidak ditemukan field NOMOR_PETA pada layer grid Anda.")
                
            # Cek apakah source grid_source beda CRS dengan AOI?
            crs_grid = grid_source.sourceCrs()
            crs_aoi = aoi_source.sourceCrs()
            transform_to_aoi = None
            if crs_grid != crs_aoi:
                transform_to_aoi = QgsCoordinateTransform(crs_grid, crs_aoi, context.transformContext())
                
            # Loop index grid, check intersects
            for grid_feat in grid_source.getFeatures():
                if grid_feat.hasGeometry():
                    grid_geom = grid_feat.geometry()
                    if transform_to_aoi:
                        grid_geom.transform(transform_to_aoi)
                        
                    for aoi_geom in aoi_geoms:
                        if grid_geom.intersects(aoi_geom):
                            nomor = grid_feat.attribute(target_idx)
                            if nomor:
                                nomor_petas.add(str(nomor))
                            break
                            
        if not nomor_petas:
            feedback.pushWarning("Tidak ada nomor peta yang ditemukan di area tersebut.")
            return {}
            
        if filter_str:
            allowed = [x.strip() for x in filter_str.split(',') if x.strip()]
            if allowed:
                nomor_petas = {n for n in nomor_petas if n in allowed}
                if not nomor_petas:
                    feedback.pushWarning("Semua grid yang ditemukan telah diabaikan oleh Filter Nomor Peta.")
                    return {}
            
        feedback.pushInfo(f"Ditemukan {len(nomor_petas)} grid yang akan diunduh: {', '.join(nomor_petas)}")
        
        headers = {
            "Accept": "application/json, text/plain, */*",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) QGIS/3.x"
        }
        
        import concurrent.futures
        
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        
        def download_file(nomor):
            dl_url = f"https://tanahair.indonesia.go.id/api-inageo/unduh/cetak?token={token}&filename={nomor}&skala={skala}"
            out_file = os.path.join(out_folder, f"RBI_{skala}_{nomor}.zip")
            
            max_retries = 3
            retry_count = 0
            
            while retry_count < max_retries:
                try:
                    current_size = 0
                    req_headers = headers.copy()
                    
                    if os.path.exists(out_file):
                        current_size = os.path.getsize(out_file)
                        if current_size > 0:
                            if retry_count == 0:
                                return f"[SKIP] File {os.path.basename(out_file)} sudah ada."
                            else:
                                req_headers['Range'] = f"bytes={current_size}-"
                                
                    req = urllib.request.Request(dl_url, headers=req_headers)
                    with urllib.request.urlopen(req, context=ctx, timeout=60) as response:  # nosec B310
                        mode = 'ab' if response.status == 206 else 'wb'
                        with open(out_file, mode) as f:
                            while True:
                                chunk = response.read(65536)
                                if not chunk:
                                    break
                                f.write(chunk)
                    return f"[SUKSES] {nomor} diunduh."
                except Exception as e:
                    retry_count += 1
                    time.sleep(2)
                    if retry_count >= max_retries:
                        return f"[GAGAL] {nomor}: {str(e)}"
                
        feedback.pushInfo(f"Mulai mengunduh {len(nomor_petas)} grid (Maks 3 paralel)...")
        step = 100.0 / len(nomor_petas) if len(nomor_petas) > 0 else 100
        current = 0
        
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            future_to_nomor = {executor.submit(download_file, nomor): nomor for nomor in nomor_petas}
            for future in concurrent.futures.as_completed(future_to_nomor):
                if feedback.isCanceled():
                    break
                res = future.result()
                feedback.pushInfo(res)
                current += step
                feedback.setProgress(int(current))
            
        feedback.pushInfo("Selesai.")
        try:
            os.startfile(os.path.normpath(out_folder))  # nosec B606
        except Exception as e:
            feedback.pushInfo(str(e))
            
        return {}

    def name(self):
        return 'downloadpetacetakrbi'

    def displayName(self):
        return '6. Unduh Peta Cetak RBI'

    def group(self):
        return 'BIG Downloader Tools'

    def groupId(self):
        return 'big_downloader_v2'

    def shortHelpString(self):
        return (
            "Download Peta Cetak RBI (format PDF/JPG) berdasarkan Area of Interest (AOI).\n\n"
            "Tool ini akan mencari indeks nomor peta cetak RBI (skala 25K, 50K, 250K) "
            "yang bersinggungan dengan poligon AOI Anda dan mengunduhnya secara otomatis."
        )

    def icon(self):
        import os
        from qgis.PyQt.QtGui import QIcon
        icon_path = os.path.join(os.path.dirname(__file__), '..', 'images', 'icon_peta_cetak.png')
        return QIcon(os.path.abspath(icon_path))

    def createInstance(self):
        return DownloadPetaCetakRBI()
