# -*- coding: utf-8 -*-
import os
import time
import tempfile
import shutil
import urllib.request
import ssl
from qgis.core import (
    QgsProcessing,
    QgsProcessingAlgorithm,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterString,
    QgsProcessingParameterFolderDestination,
    QgsProcessingParameterBoolean,
    QgsProcessingParameterCrs,
    QgsProcessingParameterNumber,
    QgsFeatureRequest,
    QgsGeometry,
    QgsCoordinateTransform,
    QgsProject,
    QgsProcessingException,
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransformContext
)
from qgis import processing
from .license_guard import SupabaseGuard

class DownloadDemnasPro(QgsProcessingAlgorithm):
    INDEX_DEM = 'INDEX_DEM'
    AOI = 'AOI'
    BUFFER_DISTANCE = 'BUFFER_DISTANCE'
    TOKEN = 'TOKEN'
    OUTPUT_FOLDER = 'OUTPUT_FOLDER'
    OUTPUT_NAME = 'OUTPUT_NAME'
    CLIP_TO_AOI = 'CLIP_TO_AOI'
    OUTPUT_CRS = 'OUTPUT_CRS'

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFeatureSource(
            self.INDEX_DEM,
            'DEMNAS Index Grid',
            [QgsProcessing.TypeVectorPolygon]
        ))
        
        self.addParameter(QgsProcessingParameterFeatureSource(
            self.AOI,
            'Area of Interest (AOI)',
            [QgsProcessing.TypeVectorPolygon]
        ))
        

        
        self.addParameter(QgsProcessingParameterString(
            self.TOKEN,
            'API Token'
        ))
        
        self.addParameter(QgsProcessingParameterFolderDestination(
            self.OUTPUT_FOLDER,
            'Output Folder'
        ))
        
        self.addParameter(QgsProcessingParameterString(
            self.OUTPUT_NAME,
            'Output Name (without extension)',
            defaultValue='DEMNAS_Output'
        ))
        
        self.addParameter(QgsProcessingParameterBoolean(
            self.CLIP_TO_AOI,
            'Clip DEM with AOI?',
            defaultValue=True
        ))
        
        self.addParameter(QgsProcessingParameterNumber(
            self.BUFFER_DISTANCE,
            'Buffer Distance (in AOI units)',
            type=QgsProcessingParameterNumber.Double,
            defaultValue=100.0,
            optional=True
        ))
        
        self.addParameter(QgsProcessingParameterCrs(
            self.OUTPUT_CRS,
            'Output Coordinate System',
            optional=True
        ))

    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. Please verify your license.")

        # 2. Get Parameters
        index_source = self.parameterAsSource(parameters, self.INDEX_DEM, context)
        aoi_source = self.parameterAsSource(parameters, self.AOI, context)
        buffer_distance = self.parameterAsDouble(parameters, self.BUFFER_DISTANCE, context)
        token = self.parameterAsString(parameters, self.TOKEN, context)
        output_folder = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context)
        output_name = self.parameterAsString(parameters, self.OUTPUT_NAME, context)
        clip_dem = self.parameterAsBool(parameters, self.CLIP_TO_AOI, context)
        output_crs = self.parameterAsCrs(parameters, self.OUTPUT_CRS, context)

        # Handle Temporary Output
        if output_folder == 'TEMPORARY_OUTPUT':
             output_folder = os.path.join(tempfile.gettempdir(), 'BIG_DEMNAS_Output')
             if not os.path.exists(output_folder):
                 os.makedirs(output_folder)
             feedback.pushInfo(f"Using temporary folder: {output_folder}")

        if not index_source or not aoi_source:
            raise Exception("Invalid input layers")

        # 3. Find Intersecting Tiles
        feedback.pushInfo("Analyzing intersection...")
        aoi_features = list(aoi_source.getFeatures())
        
        # Verify for user that selection is working
        feedback.pushInfo(f"Memproses {len(aoi_features)} fitur AOI (Selected only: {'Ya' if aoi_source.hasFeatures() else 'Tidak/Limit'}).")
        
        if not aoi_features:
            raise Exception("AOI layer contains no features")
            
        # Combine AOI geometries
        aoi_geom = QgsGeometry.unaryUnion([f.geometry() for f in aoi_features])
        
        # Temp dir for processing
        temp_dir = tempfile.mkdtemp(prefix='demnas_qgis_')
        temp_aoi_path = os.path.join(temp_dir, "temp_aoi.gpkg")
        
        # Apply Buffer (Project to Meters usually 3857) if needed
        if buffer_distance and buffer_distance > 0:
            feedback.pushInfo(f"Applying buffer: {buffer_distance} meters (via EPSG:3857)...")
            
            source_crs = aoi_source.sourceCrs()
            metric_crs = QgsCoordinateReferenceSystem("EPSG:3857")
            
            # Transform to Metric
            xform_to_metric = QgsCoordinateTransform(source_crs, metric_crs, context.project())
            aoi_geom.transform(xform_to_metric)
            
            # Buffer
            aoi_geom = aoi_geom.buffer(buffer_distance, 5)
            
            # Transform back to Source (or Index CRS if diff) logic handled below
            # But we need to save THIS buffered geometry for clipping later
            # So transform back to Source CRS first to keep it consistent
            xform_to_source = QgsCoordinateTransform(metric_crs, source_crs, context.project())
            aoi_geom.transform(xform_to_source)

        # Save this (potentially buffered) AOI for Clipping later
        # We save it in input CRS, reprojecting only if needed later
        # Create temp vector layer to save aoi_geom
        from qgis.core import QgsVectorFileWriter, QgsFields
        
        save_options = QgsVectorFileWriter.SaveVectorOptions()
        save_options.driverName = "GPKG"
        warn_msg = ""
        
        writer = QgsVectorFileWriter.create(
            temp_aoi_path,
            aoi_source.fields(),
            aoi_source.wkbType(),
            aoi_source.sourceCrs(),
            QgsCoordinateTransformContext(),
            save_options
        )
        
        if writer.hasError() != QgsVectorFileWriter.NoError:
             raise Exception(f"Error creating temp AOI: {writer.errorMessage()}")
             
        # Add feature
        from qgis.core import QgsFeature
        feat = QgsFeature()
        feat.setGeometry(aoi_geom)
        writer.addFeature(feat)
        del writer # Flush
        
        # Transform AOI to Index CRS for selection
        if index_source.sourceCrs() != aoi_source.sourceCrs():
            xform = QgsCoordinateTransform(aoi_source.sourceCrs(), index_source.sourceCrs(), context.project())
            aoi_geom.transform(xform)

        # Spatial query
        request = QgsFeatureRequest().setFilterRect(aoi_geom.boundingBox())
        tile_names = []
        for feat in index_source.getFeatures(request):
            if feat.geometry().intersects(aoi_geom):
                # Try finding name field
                name_idx = feat.fieldNameIndex('Name')
                if name_idx == -1: name_idx = feat.fieldNameIndex('NAME')
                if name_idx == -1: name_idx = feat.fieldNameIndex('name')
                
                if name_idx != -1:
                    tile_names.append(feat.attributes()[name_idx])

        feedback.pushInfo(f"Ditemukan {len(tile_names)} tile untuk diunduh.")
        if not tile_names:
            return {}

        # 4. Download Tiles
        feedback.pushInfo("\n=== MENGUNDUH TILE ===")
        # Use the same temp_dir created earlier for AOI
        # temp_dir = tempfile.mkdtemp(prefix='demnas_qgis_') 
        downloaded_files = []
        
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        total = len(tile_names)
        for i, tile_name in enumerate(tile_names):
            if feedback.isCanceled():
                break
                
            feedback.setProgress(int((i/total)*100))
            # feedback.pushInfo(f"Downloading {tile_name} ({i+1}/{total})...")
            
            url = f"https://tanahair.indonesia.go.id/api-inageo/unduh/demnas?token={token}&filename={tile_name}.tif"
            out_file = os.path.join(temp_dir, f"{tile_name}.tif")
            
            try:
                req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
                with urllib.request.urlopen(req, context=ctx, timeout=300) as response:
                    with open(out_file, 'wb') as f:
                        f.write(response.read())
                
                if os.path.exists(out_file) and os.path.getsize(out_file) > 0:
                    downloaded_files.append(out_file)
                    feedback.pushInfo(f"Berhasil: {tile_name}")
                else:
                    feedback.reportError(f"Gagal (File Kosong): {tile_name}")
                    
            except Exception as e:
                feedback.reportError(f"Gagal {tile_name}: {str(e)}")

        if not downloaded_files:
            raise Exception("Tidak ada tile yang berhasil diunduh.")

        # 5. Mosaic
        feedback.pushInfo("\n=== MEMBUAT MOSAIK ===")
        mosaic_out = os.path.join(temp_dir, "mosaic.tif")
        
        # Use GDAL Warp (Mosaic) to ensure locks are released
        # processing.run can hold locks.
        from osgeo import gdal
        
        feedback.pushInfo(f"Merging tiles with GDAL Warp...")
        try:
            warp_opts = gdal.WarpOptions(
                format='GTiff',
                outputType=gdal.GDT_Float32,
                dstNodata=-9999,
                resampleAlg=gdal.GRA_Bilinear
            )
            ds = gdal.Warp(mosaic_out, downloaded_files, options=warp_opts)
            ds = None # Release locks
            
            # Helper: QGIS usually returns a dict from processing.run, here we just use the path
            final_raster = mosaic_out
            
        except Exception as e:
            raise QgsProcessingException(f"Gagal mosaik DEMNAS: {e}")
        
        # 6. Clip / Reproject / Save
        feedback.pushInfo("\n=== MENYIMPAN OUTPUT ===")
        
        # Handle Temporary Output
        if output_folder == 'TEMPORARY_OUTPUT':
             output_folder = os.path.join(tempfile.gettempdir(), 'BIG_DEMNAS_Output')
             feedback.pushInfo(f"Info: Menggunakan folder sementara: {output_folder}")
        
        # Ensure output directory exists (Crucial for QGIS temp paths)
        if not os.path.exists(output_folder):
            try:
                os.makedirs(output_folder, exist_ok=True)
            except Exception as e:
                 feedback.pushInfo(f"Peringatan membuat folder: {e}")

        # Determine Output Filename with Suffixes
        final_name = output_name
        
        # 1. Add _clip suffix if clipping enabled
        if clip_dem:
            final_name += "_clip"
            
        # 2. Add _utm (or _projected) suffix if reprojecting to non-4326
        # Logic: if output CRS is valid AND different from WGS84 (EPSG:4326)
        if output_crs.isValid():
             if output_crs.authid() != "EPSG:4326":
                  final_name += "_utm" 
        
        dest_path = os.path.join(output_folder, f"{final_name}.tif")
        
        if clip_dem:
             # Fix for "Cannot find source SRS" error:
             # 1. Use the temp_aoi_path (result of buffer/union) which is a GPKG
             # 2. Key: We MUST Reproject this GPKG to match RASTER CRS before clipping
             #    because gdal clip works best when mask and raster share CRS / same projection.
             
             feedback.pushInfo("Menyiapkan AOI untuk clipping...")
             
             # The buffered AOI is in temp_aoi_path (in Source/AOI CRS)
             # We want to reproject it to Raster CRS for clipping
             
             # Get raster CRS
             from qgis.core import QgsRasterLayer
             raster_layer = QgsRasterLayer(final_raster, "temp")
             raster_crs = raster_layer.crs()
             
             reprojected_aoi_mask = os.path.join(temp_dir, "aoi_mask_reprojected.gpkg")

             # Reproject AOI to match raster CRS
             reproject_params = {
                 'INPUT': temp_aoi_path, # Use the buffered AOI file
                 'TARGET_CRS': raster_crs,
                 'OUTPUT': reprojected_aoi_mask
             }
             # Suppress feedback for intermediate step
             processing.run("native:reprojectlayer", reproject_params, context=context)
             
             feedback.pushInfo(f"Memotong raster sesuai AOI...")

             # Clip using the reprojected file
             clip_params = {
                 'INPUT': final_raster,
                 'MASK': reprojected_aoi_mask, 
                 'SOURCE_CRS': raster_crs,
                 'TARGET_CRS': raster_crs,
                 'NODATA': -9999,
                 'ALPHA_BAND': False,
                 'CROP_TO_CUTLINE': True,
                 'KEEP_RESOLUTION': True,
                 'OPTIONS': '',
                 'DATA_TYPE': 0,
                 'OUTPUT': dest_path
             }
             # Suppress verbose GDAL output by not passing feedback unless error
             try:
                 processing.run("gdal:cliprasterbymasklayer", clip_params, context=context)
             except Exception as e:
                 feedback.reportError(f"Gagal saat clipping: {e}")
                 raise e

        else:
             # If just copy/reproject
             if output_crs.isValid():
                 feedback.pushInfo(f"Reproyeksi ke {output_crs.authid()}...")
                 warp_params = {
                     'INPUT': final_raster,
                     'SOURCE_CRS': None,
                     'TARGET_CRS': output_crs,
                     'NODATA': -9999,
                     'options': '',
                     'DATA_TYPE': 0,
                     'OUTPUT': dest_path
                 }
                 try:
                     processing.run("gdal:warpreproject", warp_params, context=context)
                 except Exception as e:
                     raise e
             else:
                 feedback.pushInfo("Menyalin output...")
                 shutil.copy2(final_raster, dest_path)

        # Cleanup
        # Robust cleanup loop
        if os.path.exists(temp_dir):
            for i in range(3):
                try:
                    shutil.rmtree(temp_dir, ignore_errors=True)
                    if not os.path.exists(temp_dir):
                        break
                    time.sleep(1)
                except:
                    time.sleep(1)
        
        feedback.pushInfo(f"Selesai! Output tersimpan di: {dest_path}")
        
        # Explicitly load the layer to QGIS
        if os.path.exists(dest_path):
            from qgis.core import QgsProcessingContext
            # Use final_name for the layer name so it shows suffixes in TOC
            details = QgsProcessingContext.LayerDetails(final_name, context.project(), final_name)
            context.addLayerToLoadOnCompletion(dest_path, details)
        
        return {self.OUTPUT_NAME: dest_path}

    def name(self):
        return 'download_demnas_pro'

    def displayName(self):
        return '2. Download DEMNAS (Mosaic)'

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

    def groupId(self):
        return 'big_downloader_v2'

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

    def createInstance(self):
        return DownloadDemnasPro()
