# -*- coding: utf-8 -*-
import os
import urllib.request
import urllib.parse
import urllib.error
import ssl
import time
import tempfile
import shutil
import re
from qgis.core import (
    QgsProcessing,
    QgsProcessingAlgorithm,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterString,
    QgsProcessingParameterFolderDestination,
    QgsProcessingParameterBoolean,
    QgsProcessingParameterCrs,
    QgsProcessingParameterEnum
)
from qgis import processing
from .license_guard import SupabaseGuard


class DownloadBATNAS(QgsProcessingAlgorithm):
    MODE = 'MODE'
    ONLINE_GRIDS = 'ONLINE_GRIDS'
    INPUT_GRID = 'INPUT_GRID'
    TOKEN = 'TOKEN'  # nosec B105
    OUTPUT_FOLDER = 'OUTPUT_FOLDER'
    OUTPUT_CRS = 'OUTPUT_CRS'
    DATA_VERSION = 'DATA_VERSION'
    CREATE_MOSAIC = 'CREATE_MOSAIC'

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterEnum(
            self.MODE, 'Mode Pengambilan Grid',
            options=['Online (Ketik Nama Grid Secara Manual)', 'Offline (Berdasarkan Layer Index)'],
            defaultValue=0
        ))

        self.addParameter(QgsProcessingParameterString(
            self.ONLINE_GRIDS, 'Nama Grid BATNAS (Khusus Mode Online, pisahkan dengan koma. Kosong = Semua)',
            optional=True
        ))

        self.addParameter(QgsProcessingParameterFeatureSource(
            self.INPUT_GRID, 'Layer Index Grid (Khusus Mode Offline)', [QgsProcessing.TypeVectorPolygon],
            optional=True
        ))

        self.addParameter(QgsProcessingParameterString(
            self.TOKEN, 'API Token'
        ))

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

        self.addParameter(QgsProcessingParameterCrs(
            self.OUTPUT_CRS, 'Output Coordinate System (Opsional)', optional=True
        ))

        self.addParameter(QgsProcessingParameterEnum(
            self.DATA_VERSION, 'Data Version',
            options=['1.1', '1.5'],
            defaultValue=1
        ))

        self.addParameter(QgsProcessingParameterBoolean(
            self.CREATE_MOSAIC, 'Create Mosaic of All Tiles', defaultValue=True
        ))

    def clean_raster_name(self, name):
        clean = re.sub(r'_v\d+\.\d+\.tif$', '', name, flags=re.IGNORECASE)
        clean = re.sub(r'[^a-zA-Z0-9]', '_', clean)
        return clean[:50]

    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)
        mode = "Online" if mode_idx == 0 else "Offline"

        online_grids_str = self.parameterAsString(parameters, self.ONLINE_GRIDS, context)
        input_source = self.parameterAsSource(parameters, self.INPUT_GRID, context)
        token = self.parameterAsString(parameters, self.TOKEN, context)
        output_folder = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context)
        output_crs = self.parameterAsCrs(parameters, self.OUTPUT_CRS, context)
        
        version_idx = self.parameterAsEnum(parameters, self.DATA_VERSION, context)
        data_version = "1.5" if version_idx == 1 else "1.1"
        
        create_mosaic = self.parameterAsBool(parameters, self.CREATE_MOSAIC, context)

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

        # Gather tiles to download
        tile_names = []
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        if mode == "Online":
            feedback.pushInfo("Mode: Online")
            if online_grids_str.strip():
                # Parse manual string
                grids = [x.strip() for x in online_grids_str.split(',')]
                for g in grids:
                    if g:
                        tile_names.append(g)
            else:
                feedback.pushInfo("Daftar grid kosong, mengambil semua grid dari server BIG...")
                import json
                url = "https://tanahair.indonesia.go.id/portal-web/batnas.json"
                req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
                try:
                    with urllib.request.urlopen(req, context=ctx, timeout=60)  # nosec B310 as response:
                        res_text = response.read().decode('utf-8')
                        data = json.loads(res_text)
                        for feat in data.get('features', []):
                            namobj = feat.get('properties', {}).get('Penamaan')
                            if namobj:
                                tile_names.append(namobj)
                except Exception as e:
                    raise Exception(f"Gagal mengambil metadata BATNAS dari server: {e}")
                    
            if not tile_names:
                raise Exception("Tidak ada grid yang akan diunduh.")
        else:
            feedback.pushInfo("Mode: Offline")
            if not input_source:
                raise Exception("Input Layer Index Grid belum dipilih untuk Mode Offline!")
            
            # Cari field Penamaan
            field_name = None
            fields = input_source.fields()
            for f in fields:
                if f.name().lower() in ['penamaan', 'name', 'namobj']:
                    field_name = f.name()
                    break
            
            if not field_name:
                raise Exception("Field 'Penamaan' atau 'NAMOBJ' tidak ditemukan pada layer grid lokal.")
                
            for feat in input_source.getFeatures():
                val = feat.attribute(field_name)
                if val:
                    tile_names.append(str(val).strip())

        # Create a specific temp dir for tiles to avoid polluting output
        tiles_temp_dir = tempfile.mkdtemp(prefix='batnas_tiles_')
        feedback.pushInfo(f"Downloading {len(tile_names)} tiles to temporary folder...")

        downloaded = []
        current = 0
        total_feats = len(tile_names)

        try:
            for grid_name in tile_names:
                if feedback.isCanceled():
                    break
                current += 1
                feedback.setProgress(int((current / total_feats) * 100))

                filename = f"BATNAS_{grid_name}_MSL_v{data_version}.tif"
                clean = self.clean_raster_name(filename)

                temp_path = os.path.join(tiles_temp_dir, f"{clean}.tif")
                url = f"https://tanahair.indonesia.go.id/api-inageo/unduh/batnas?token={token}&filename={urllib.parse.quote(filename)}"

                max_retries = 3
                success = False

                for attempt in range(max_retries):
                    try:
                        req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
                        
                        current_size = 0
                        if os.path.exists(temp_path) and attempt > 0:
                            current_size = os.path.getsize(temp_path)
                            if current_size > 0:
                                req.add_header('Range', f"bytes={current_size}-")

                        with urllib.request.urlopen(req, context=ctx, timeout=7200)  # nosec B310 as response:
                            if response.status == 404:
                                raise Exception("HTTP 404: File tidak ditemukan di server BIG.")
                            if response.status not in (200, 206):
                                raise Exception(f"HTTP Error {response.status}")
                                
                            write_mode = 'ab' if response.status == 206 else 'wb'
                            with open(temp_path, write_mode) as f:
                                while True:
                                    if feedback.isCanceled(): break
                                    chunk = response.read(65536)
                                    if not chunk: break
                                    f.write(chunk)

                        if os.path.exists(temp_path) and os.path.getsize(temp_path) > 0:
                            downloaded.append(temp_path)
                            feedback.pushInfo(f"Berhasil: {clean}")
                            success = True
                            break
                        else:
                            if attempt < max_retries - 1:
                                time.sleep(2)
                                continue
                            else:
                                feedback.reportError(f"Gagal (File Kosong): {filename}")

                    except (urllib.error.HTTPError, urllib.error.URLError, Exception) as e:
                        if isinstance(e, urllib.error.HTTPError) and e.code == 401:
                            raise Exception("HTTP Error 401: Unauthorized. Token API Anda mungkin kadaluarsa.")
                        if attempt < max_retries - 1:
                            feedback.pushInfo(f"Gagal {filename} (Attempt {attempt + 1}/{max_retries}): {str(e)}. Retrying...")
                            time.sleep(2)
                        else:
                            feedback.reportError(f"Gagal {filename}: {str(e)}")

                if not success:
                    pass

            if not downloaded:
                raise Exception("Tidak ada file yang berhasil diunduh.")

            # 3. Process Output
            if create_mosaic:
                feedback.pushInfo("\n=== MEMBUAT MOSAIK ===")
                if not os.path.exists(output_folder):
                    try: os.makedirs(output_folder, exist_ok=True)
                    except: pass

                mosaic_out = os.path.join(output_folder, "BATNAS_Mosaic.tif")
                from osgeo import gdal

                feedback.pushInfo(f"Merging {len(downloaded)} tiles using GDAL Warp (WGS84)...")

                try:
                    # BATNAS IS ALWAYS WGS84, explicitly set dstSRS to EPSG:4326 to fix CRS warnings
                    warp_opts = gdal.WarpOptions(
                        format='GTiff',
                        outputType=gdal.GDT_Float32,
                        dstSRS='EPSG:4326',
                        dstNodata=-9999,
                        resampleAlg=gdal.GRA_Bilinear
                    )
                    gdal.Warp(mosaic_out, downloaded, options=warp_opts)
                except Exception as e:
                    from qgis.core import QgsProcessingException
                    raise QgsProcessingException(f"Gagal membuat mosaik dengan GDAL Warp: {e}")

                if output_crs.isValid() and output_crs.authid() != 'EPSG:4326':
                    feedback.pushInfo(f"Reprojecting mosaic to {output_crs.authid()}...")
                    proj_out = os.path.join(output_folder, "BATNAS_Mosaic_Proj.tif")
                    warp_params = {
                        'INPUT': mosaic_out,
                        'TARGET_CRS': output_crs,
                        'NODATA': -9999,
                        'OUTPUT': proj_out
                    }
                    processing.run("gdal:warpreproject", warp_params, context=context, feedback=feedback)
                    if os.path.exists(mosaic_out):
                        try: os.remove(mosaic_out)
                        except: pass
                    mosaic_out = proj_out

                feedback.pushInfo(f"Mosaic berhasil dibuat: {mosaic_out}")
                if os.path.exists(mosaic_out):
                    from qgis.core import QgsProcessingContext
                    details = QgsProcessingContext.LayerDetails("BATNAS Mosaic", context.project(), "BATNAS_Mosaic")
                    context.addLayerToLoadOnCompletion(mosaic_out, details)

                return {self.OUTPUT_FOLDER: mosaic_out}

            else:
                feedback.pushInfo("\n=== MENYIMPAN FILE ===")
                if not os.path.exists(output_folder):
                    try: os.makedirs(output_folder, exist_ok=True)
                    except: pass

                for t_path in downloaded:
                    fname = os.path.basename(t_path)
                    out_path = os.path.join(output_folder, fname)

                    if output_crs.isValid():
                        warp_params = {
                            'INPUT': t_path,
                            'TARGET_CRS': output_crs,
                            'NODATA': -9999,
                            'OUTPUT': out_path
                        }
                        processing.run("gdal:warpreproject", warp_params, context=context, feedback=feedback)
                    else:
                        # Ensure WGS84 explicitly even if not reprojecting
                        warp_opts = gdal.WarpOptions(format='GTiff', dstSRS='EPSG:4326')
                        gdal.Warp(out_path, t_path, options=warp_opts)

                    if os.path.exists(out_path):
                        from qgis.core import QgsProcessingContext
                        details = QgsProcessingContext.LayerDetails(fname, context.project(), fname)
                        context.addLayerToLoadOnCompletion(out_path, details)

                return {self.OUTPUT_FOLDER: output_folder}

        finally:
            if os.path.exists(tiles_temp_dir):
                for i in range(5):
                    try:
                        shutil.rmtree(tiles_temp_dir, ignore_errors=True)
                        if not os.path.exists(tiles_temp_dir): break
                        time.sleep(1 + i)
                    except:
                        time.sleep(1)

    def name(self):
        return 'download_batnas'

    def displayName(self):
        return '4. Download BATNAS'

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

    def groupId(self):
        return 'big_downloader_v2'

    def shortHelpString(self):
        return (
            "Download data BATNAS (Batimetri Nasional) langsung dari server BIG.\n\n"
            "Pilih grid BATNAS secara manual melalui daftar yang disediakan (Filter Nama Grid). "
            "Tool ini secara otomatis akan menggabungkan (mosaic) hasil unduhan "
            "sehingga siap untuk digunakan."
        )

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

    def createInstance(self):
        return DownloadBATNAS()
