"""Configuration utilities for Trends.Earth QGIS plugin."""

import enum
import json
import os
import typing
from pathlib import Path

import qgis.core
from qgis.PyQt import QtCore
from te_schemas.algorithms import ExecutionScript

from . import download
from .algorithms import models as algorithm_models
from .reports.utils import default_report_disclaimer
from .utils import FileUtils


class tr_conf(QtCore.QObject):
    def tr(self, txt):
        return QtCore.QCoreApplication.translate(self.__class__.__name__, txt)


tr_conf = tr_conf()


TR_ALL_REGIONS = tr_conf.tr("All regions")

OPTIONS_ICON = "trends_earth_logo_square_32x32.ico"
OPTIONS_TITLE = "Trends.Earth"

# Degradation Landcover matrix size settings
# If there are equal to less than this value rows than the row height will be decreased
RESIZE_NUM_ROWS = 5
# This is the minimum row height value. 40 is the cell font minimum
MINIMUM_ROW_HEIGHT = 40

DOCK_TITLE = "Trends.Earth"
DOCK_TITLE_OFFLINE = "Trends.Earth (offline mode)"


def get_dock_title(offline_mode: bool = False) -> str:
    """Get the dock widget title, including custom API URL if configured.

    Args:
        offline_mode: Whether the plugin is in offline mode.

    Returns:
        str: The dock title, optionally including custom API URL or offline indicator.
    """
    from .constants import DEFAULT_API_URL, get_api_url

    base_title = DOCK_TITLE
    api_url = get_api_url()

    # Build title with custom API URL if not default
    if api_url != DEFAULT_API_URL:
        base_title = f"{DOCK_TITLE} ({api_url})"

    # Add offline mode indicator if applicable
    if offline_mode:
        if api_url != DEFAULT_API_URL:
            return f"{base_title} - offline"
        else:
            return DOCK_TITLE_OFFLINE

    return base_title


class AreaSetting(enum.Enum):
    COUNTRY_REGION = "country_region"
    COUNTRY_CITY = "country_city"
    POINT = "point"
    VECTOR_LAYER = "vector_layer"


class Setting(enum.Enum):
    LOCAL_POLLING_FREQUENCY = "private/local_polling_frequency_seconds"
    UPDATE_FREQUENCY_MILLISECONDS = "private/update_frequency_milliseconds"
    UNKNOWN_AREA_OF_INTEREST = "private/unknown_area_of_interest"
    PRIOR_LOCALE = "private/prior_locale"
    PLOTLY_MISSING_WARNING_LAST_VERSION = "private/plotly_missing_warning_last_version"

    FILTERS_ENABLED = "filters/filters_enabled"
    DATE_FILTER_ENABLED = "filters/date_filter_enabled"
    FILTER_START_DATE = "filters/start_date"
    FILTER_END_DATE = "filters/end_date"
    STATUS_FILTER_ENABLED = "filters/status_filter_enabled"
    STATUS_FILTER_VALUE = "filters/status_filter_value"
    TASK_TYPE_FILTER_ENABLED = "filters/task_type_filter_enabled"
    TASK_TYPE_FILTER_VALUE = "filters/task_type_filter_value"

    DEBUG = "advanced/debug"

    FILTER_JOBS_BY_BASE_DIR = "advanced/FILTER_JOBS_BY_BASE_DIR"
    BASE_DIR = "advanced/base_data_directory"
    CUSTOM_CRS_ENABLED = "region_of_interest/custom_crs_enabled"
    CUSTOM_CRS = "region_of_interest/custom_crs"
    CSV_FILE_DIR = "advanced/csv_file_dir"
    POLL_REMOTE = "advanced/poll_remote_server"
    REMOTE_POLLING_FREQUENCY = "advanced/remote_polling_frequency_seconds"
    DOWNLOAD_RESULTS = "advanced/download_remote_results_automatically"
    OFFLINE_MODE = "advanced/offline_mode"
    CUSTOM_API_URL = "advanced/custom_api_url"
    USER_ID = "authentication/user_id"
    BUFFER_CHECKED = "region_of_interest/buffer_checked"
    AREA_FROM_OPTION = "region_of_interest/chosen_method"
    POINT_X = "region_of_interest/point/x"
    POINT_Y = "region_of_interest/point/y"
    VECTOR_FILE_PATH = "region_of_interest/vector_file"
    VECTOR_FILE_DIR = "region_of_interest/vector_file_dir"
    # Legacy name-based settings (deprecated - use ID-based settings below)
    COUNTRY_NAME = "region_of_interest/country/country_name"
    REGION_NAME = "region_of_interest/country/region_name"
    CITY_NAME = "region_of_interest/country/city_name"
    CITY_KEY = "region_of_interest/current_cities_key"
    # ID-based boundary settings (primary storage)
    COUNTRY_ID = "region_of_interest/country/country_id"
    REGION_ID = "region_of_interest/country/region_id"
    CITY_ID = "region_of_interest/country/city_id"
    BUFFER_SIZE = "region_of_interest/buffer_size"
    AREA_NAME = "region_of_interest/area_settings_name"
    JOB_FILE_AGE_LIMIT_DAYS = "advanced/deleted_datasets_age_limit"
    DEFINITIONS_DIRECTORY = "advanced/definitions_directory"
    REPORT_TEMPLATE_SEARCH_PATH = "report/template_search_path"
    REPORT_ORG_LOGO_PATH = "report/org_logo_path"
    REPORT_ORG_NAME = "report/org_name"
    REPORT_FOOTER = "report/footer"
    REPORT_DISCLAIMER = "report/disclaimer"
    REPORT_LOG_WARNING = "report/log_warning"
    LC_CLASSES = "land_cover/user_classes"
    LC_MAX_CLASSES = "land_cover/max_classes_v3"
    LC_LAST_DIR = "land_cover/last_dir"
    LC_IPCC_NESTING = "land_cover/ipcc_nesting"
    LC_ESA_NESTING = "land_cover/esa_nesting"
    LC_CUSTOM_IMPORT_NESTING = "land_cover/custom_import_nesting"
    IMPORT_AREA_TOLERANCE = "import/extent_tolerance"


class SettingsManager:
    _settings: qgis.core.QgsSettings
    _base_path: str = "trends_earth"
    _base_data_path: str = "trends_earth_data"

    DEFAULT_SETTINGS = {
        Setting.UPDATE_FREQUENCY_MILLISECONDS: 10000,
        Setting.LOCAL_POLLING_FREQUENCY: 30,
        Setting.UNKNOWN_AREA_OF_INTEREST: "unknown-area",
        Setting.PRIOR_LOCALE: "unknown",
        Setting.PLOTLY_MISSING_WARNING_LAST_VERSION: "",
        Setting.REMOTE_POLLING_FREQUENCY: 3 * 60,
        Setting.DEBUG: False,
        Setting.FILTER_JOBS_BY_BASE_DIR: True,
        Setting.OFFLINE_MODE: False,
        Setting.BASE_DIR: str(Path.home() / _base_data_path),
        Setting.DEFINITIONS_DIRECTORY: str(
            Path.home() / _base_data_path / "definitions"
        ),
        Setting.CUSTOM_CRS_ENABLED: False,
        Setting.CUSTOM_CRS: "epsg:4326",
        Setting.POLL_REMOTE: True,
        Setting.DOWNLOAD_RESULTS: True,
        Setting.CUSTOM_API_URL: "",
        Setting.FILTERS_ENABLED: False,
        Setting.DATE_FILTER_ENABLED: False,
        Setting.FILTER_START_DATE: "",
        Setting.FILTER_END_DATE: "",
        Setting.STATUS_FILTER_ENABLED: False,
        Setting.STATUS_FILTER_VALUE: "",
        Setting.TASK_TYPE_FILTER_ENABLED: False,
        Setting.TASK_TYPE_FILTER_VALUE: "",
        Setting.BUFFER_CHECKED: False,
        Setting.AREA_FROM_OPTION: AreaSetting.COUNTRY_REGION.value,
        Setting.POINT_X: 0.0,
        Setting.POINT_Y: 0.0,
        Setting.VECTOR_FILE_PATH: "",
        Setting.VECTOR_FILE_DIR: "",
        Setting.CSV_FILE_DIR: "",
        Setting.COUNTRY_NAME: "",
        Setting.REGION_NAME: "",
        Setting.CITY_NAME: "",
        Setting.CITY_KEY: 0,
        # ID-based boundary settings (primary storage)
        Setting.COUNTRY_ID: "",
        Setting.REGION_ID: "",
        Setting.CITY_ID: "",
        Setting.BUFFER_SIZE: 0.0,
        Setting.AREA_NAME: "",
        Setting.USER_ID: None,
        Setting.JOB_FILE_AGE_LIMIT_DAYS: 15,
        Setting.REPORT_TEMPLATE_SEARCH_PATH: "",
        Setting.REPORT_ORG_LOGO_PATH: FileUtils.te_logo_path(),
        Setting.REPORT_ORG_NAME: "",
        Setting.REPORT_FOOTER: "",
        Setting.REPORT_DISCLAIMER: default_report_disclaimer(),
        Setting.REPORT_LOG_WARNING: False,
        Setting.LC_CLASSES: "",
        Setting.LC_MAX_CLASSES: 45,
        Setting.LC_LAST_DIR: "",
        Setting.LC_IPCC_NESTING: "",
        Setting.LC_ESA_NESTING: "",
        Setting.LC_CUSTOM_IMPORT_NESTING: "",
        Setting.IMPORT_AREA_TOLERANCE: 0.95,
    }

    def __init__(self):
        self._settings = qgis.core.QgsSettings()
        self._mutex = QtCore.QMutex()
        self._initialize_settings()

    @property
    def base_path(self):
        return self._base_path

    def get_value(self, key: Setting):
        self._mutex.lock()
        try:
            if key == Setting.LOCAL_POLLING_FREQUENCY:
                result = self.DEFAULT_SETTINGS[key]
            elif key == Setting.UPDATE_FREQUENCY_MILLISECONDS:
                result = self.DEFAULT_SETTINGS[key]
            elif key == Setting.BASE_DIR:
                type_ = type(self.DEFAULT_SETTINGS[key])
                result = self._settings.value(
                    f"{self.base_path}/{key.value}",
                    self.DEFAULT_SETTINGS[key],
                    type=type_,
                )
                if result == "" or result is None:
                    result = self.DEFAULT_SETTINGS[key]
            else:
                default_value = self.DEFAULT_SETTINGS[key]
                if default_value is None:
                    # For None default values, don't specify a type to avoid QVariant issues
                    result = self._settings.value(
                        f"{self.base_path}/{key.value}", default_value
                    )
                else:
                    type_ = type(default_value)
                    result = self._settings.value(
                        f"{self.base_path}/{key.value}", default_value, type=type_
                    )

            return result
        finally:
            self._mutex.unlock()

    def write_value(self, key: Setting, value: typing.Any):
        self._mutex.lock()
        try:
            return self._settings.setValue(f"{self.base_path}/{key.value}", value)
        finally:
            self._mutex.unlock()

    def _initialize_settings(self):
        for setting, default_value in self.DEFAULT_SETTINGS.items():
            current_value = self._settings.value(f"{self.base_path}/{setting.value}")

            if current_value is None:
                self.write_value(setting, self.DEFAULT_SETTINGS[setting])


def _load_script_config(
    script_config: typing.Dict,
) -> typing.Dict[str, ExecutionScript]:
    result = {}

    for raw_config in script_config:
        script = ExecutionScript.Schema().load(raw_config)
        result[script.name] = script

    return result


def _load_algorithm_config(
    algorithm_config: typing.List[typing.Dict],
) -> algorithm_models.AlgorithmGroup:
    top_level_groups = []

    for raw_top_level_group in algorithm_config:
        group = algorithm_models.AlgorithmGroup.deserialize(raw_top_level_group)
        top_level_groups.append(group)

    return algorithm_models.AlgorithmGroup(
        name="root", name_details="root_details", parent=None, groups=top_level_groups
    )


def _load_jsonc(file_path: str) -> typing.Any:
    """Load JSONC files by removing // comments before JSON parsing.

    Uses utf-8-sig so files with an optional UTF-8 BOM are handled cleanly.
    """

    def _strip_jsonc_comment(raw_line: str) -> str:
        """Strip // comments while preserving // inside quoted strings."""
        in_string = False
        escaped = False

        for index, char in enumerate(raw_line):
            if escaped:
                escaped = False
                continue

            if char == "\\":
                escaped = True
                continue

            if char == '"':
                in_string = not in_string
                continue

            if not in_string and char == "/" and index + 1 < len(raw_line):
                if raw_line[index + 1] == "/":
                    return raw_line[:index]

        return raw_line

    with open(file_path, encoding="utf-8-sig") as stream:
        lines = []
        for raw_line in stream:
            stripped = raw_line.lstrip()
            if stripped.startswith("//"):
                continue
            lines.append(_strip_jsonc_comment(raw_line))

    return json.loads("".join(lines))


datasets_file = os.path.join(
    os.path.dirname(os.path.realpath(__file__)), "data", "gee_datasets.jsonc"
)
REMOTE_DATASETS = _load_jsonc(datasets_file)


script_file = os.path.join(
    os.path.dirname(os.path.realpath(__file__)), "data", "scripts.json"
)
with open(script_file, encoding="utf-8") as f:
    _SCRIPT_CONFIG = json.load(f)

KNOWN_SCRIPTS = _load_script_config(_SCRIPT_CONFIG)

_ALGORITHM_CONFIG = [
    {
        "name": "SDG 15.3.1",
        "name_details": tr_conf.tr("Land degradation"),
        "algorithms": [
            {
                "id": "bdad3786-bc36-46aa-8e3d-d6cede915cef",
                "name": tr_conf.tr("Sub-indicators for SDG 15.3.1"),
                "description": tr_conf.tr(
                    "Calculate SDG 15.3.1 sub-indicators (required prior to "
                    "15.3.1 indicator calculation)"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["sdg-15-3-1-sub-indicators"],
                        "parametrization_dialogue": "LDMP.calculate_ldn.DlgCalculateOneStep",
                    }
                ],
            },
            {
                "id": "fe1cffa7-33f7-4148-ac7b-fc726402d59d",
                "name": tr_conf.tr("Indicator for SDG 15.3.1"),
                "name_details": tr_conf.tr(
                    "Spatial layer and summary table for total boundary"
                ),
                "description": tr_conf.tr(
                    "Calculate SDG 15.3.1 indicator from productivity, land "
                    "cover, and soil organic carbon sub-indicators"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["sdg-15-3-1-summary"],
                        "parametrization_dialogue": "LDMP.calculate_ldn.DlgCalculateLDNSummaryTableAdmin",
                    }
                ],
            },
            {
                "id": "e25d2a72-2274-45fa-9b69-74e87873054e",
                "name": tr_conf.tr("Land productivity"),
                "description": (
                    tr_conf.tr(
                        "Land productivity is the biological productive "
                        "capacity of land"
                    )
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["productivity"],
                        "parametrization_dialogue": "LDMP.calculate_prod.DlgCalculateProd",
                    }
                ],
            },
            {
                "id": "277f87e6-5362-4533-ab1d-c28251576884",
                "name": tr_conf.tr("Land cover change"),
                "description": tr_conf.tr(
                    "Land cover is the physical material at the surface of the earth. "
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["local-land-cover"],
                        "parametrization_dialogue": "LDMP.calculate_lc.DlgCalculateLC",
                    },
                    {
                        "script": KNOWN_SCRIPTS["land-cover"],
                        "parametrization_dialogue": "LDMP.calculate_lc.DlgCalculateLC",
                    },
                ],
            },
            {
                "id": "f32fd29b-2af8-4564-9189-3dd440758be6",
                "name": tr_conf.tr("Soil Organic Carbon"),
                "description": (
                    tr_conf.tr(
                        "Soil organic carbon is a measure of soil organic matter"
                    )
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["local-soil-organic-carbon"],
                        "parametrization_dialogue": "LDMP.calculate_soc.DlgCalculateSOC",
                    },
                    {
                        "script": KNOWN_SCRIPTS["soil-organic-carbon"],
                        "parametrization_dialogue": "LDMP.calculate_soc.DlgCalculateSOC",
                    },
                ],
            },
        ],
    },
    {
        "name": tr_conf.tr("Drought"),
        "name_details": tr_conf.tr("Vulnerability and exposure"),
        "algorithms": [
            {
                "id": "afb8d95a-20a5-11ec-9621-0242ac130002",
                "name": tr_conf.tr("Drought vulnerability"),
                "description": tr_conf.tr(
                    "Calculate indicators of drought vulnerability "
                    "consistent with UNCCD SO3 Good Practice Guidance"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["drought-vulnerability"],
                        "parametrization_dialogue": "LDMP.calculate_drought_vulnerability.DlgCalculateDrought",
                    },
                ],
            },
            {
                "id": "bb5df452-20a5-11ec-9621-0242ac130002",
                "name": tr_conf.tr("Drought vulnerability summary table"),
                "description": tr_conf.tr(
                    "Summarize drought indicators in alignment with UNCCD "
                    "SO3 reporting requirements"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["drought-vulnerability-summary"],
                        "parametrization_dialogue": "LDMP.calculate_drought_vulnerability.DlgCalculateDroughtSummary",
                    },
                ],
            },
        ],
    },
    {
        "name": tr_conf.tr("UNCCD Reporting"),
        "name_details": tr_conf.tr("Summarize data for reporting"),
        "algorithms": [
            # {
            #     "id": "052b3fbc-20a7-11ec-9621-0242ac130002",
            #     "name": "Default data for UNCCD reporting",
            #     "description": (
            #         "Generate default datasets used in the UNCCD "
            #         "2021 reporting cycle"
            #     ),
            #     "scripts": [
            #         {
            #             "script": KNOWN_SCRIPTS["unccd-default-data"],
            #             "parametrization_dialogue": "LDMP.calculate_unccd.DlgCalculateUNCCD",
            #         },
            #     ],
            # },
            {
                "id": "5293b2b2-d90f-4f1f-9556-4b0fe1c6ba91",
                "name": tr_conf.tr("Generate data package for UNCCD reporting"),
                "description": tr_conf.tr(
                    "Summarize Strategic Objective (SO) 1, SO2, and SO3 "
                    "datasets in proper format for submission to UNCCD for "
                    "2021 reporting cycle"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["unccd-report"],
                        "parametrization_dialogue": "LDMP.calculate_unccd.DlgCalculateUNCCDReport",
                    },
                ],
            },
        ],
    },
    {
        "name": tr_conf.tr("SDG 11.3.1"),
        "name_details": tr_conf.tr("Urban change and land consumption"),
        "algorithms": [
            {
                "id": "bdce0a12-c5ab-485b-ac47-278cedbce789",
                "name": tr_conf.tr("Urban change spatial layer"),
                "description": tr_conf.tr(
                    "Calculate indicators of change in urban extent "
                    "(SDG 11.3.1 indicator)"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["urban-area"],
                        "parametrization_dialogue": "LDMP.calculate_urban.DlgCalculateUrbanData",
                    },
                ],
            },
            {
                "id": "748780b4-39bb-4460-b203-0f2367d7c699",
                "name": tr_conf.tr("Urban change summary table for city"),
                "description": tr_conf.tr(
                    "Calculate table summarizing SDG indicator 11.3.1"
                ),
                "scripts": [
                    {
                        "script": KNOWN_SCRIPTS["urban-change-summary-table"],
                        "parametrization_dialogue": "LDMP.calculate_urban.DlgCalculateUrbanSummaryTable",
                    },
                ],
            },
        ],
    },
    {
        "name": tr_conf.tr("Experimental"),
        "groups": [
            {
                "name": tr_conf.tr("LDN Counterbalancing"),
                "name_details": tr_conf.tr(
                    "Assess whether gains offset losses within each land type"
                ),
                "algorithms": [
                    {
                        "id": "a3b7e940-d1c5-4f3e-8b6a-1c8f2e4d9a0b",
                        "name": tr_conf.tr("LDN Counterbalancing"),
                        "description": tr_conf.tr(
                            "Evaluate Land Degradation Neutrality counterbalancing "
                            "following the GPG Addendum methodology, assessing gains "
                            "versus losses per land type"
                        ),
                        "scripts": [
                            {
                                "script": KNOWN_SCRIPTS["ldn-counterbalancing"],
                                "parametrization_dialogue": "LDMP.calculate_counterbalancing.DlgCalculateCounterbalancing",
                            }
                        ],
                    },
                ],
            },
            {
                "name": tr_conf.tr("Calculate change in total carbon"),
                "name_details": tr_conf.tr(
                    "Above and below ground, emissions and deforestation"
                ),
                "algorithms": [
                    {
                        "id": "96f243a2-c8bd-436a-9775-424f20a1b188",
                        "name": tr_conf.tr("Calculate change in carbon"),
                        "description": tr_conf.tr(
                            "Calculate total carbon (above and below-ground) "
                            "and emissions from deforestation"
                        ),
                        "scripts": [
                            # TODO: enable and tweak this when support for local calculations of total carbon is implemented
                            # {
                            #     "script": KNOWN_SCRIPTS["local-total-carbon"],
                            #     "parametrization_dialogue": "LDMP.calculate_tc.DlgCalculateTCData",
                            # },
                            {
                                "script": KNOWN_SCRIPTS["total-carbon"],
                                "parametrization_dialogue": "LDMP.calculate_tc.DlgCalculateTCData",
                            },
                        ],
                    },
                    {
                        "id": "a753f2c9-be4c-4d97-9e21-09b8882e8899",
                        "name": tr_conf.tr("Change in carbon summary table"),
                        "description": tr_conf.tr(
                            "Calculate table summarizing change in total carbon"
                        ),
                        "scripts": [
                            {
                                "script": KNOWN_SCRIPTS["total-carbon-summary"],
                                "parametrization_dialogue": "LDMP.calculate_tc.DlgCalculateTCSummaryTable",
                            }
                        ],
                    },
                ],
            },
            {
                "name": tr_conf.tr("Potential change in biomass due to restoration"),
                "name_details": tr_conf.tr("Above and below ground woody"),
                "algorithms": [
                    {
                        "id": "61839d52-0d81-428d-90e6-83ea5ed3c032",
                        "name": tr_conf.tr("Estimate potential impacts of restoration"),
                        "description": tr_conf.tr(
                            "Estimate potential change in biomass due to restoration"
                        ),
                        "scripts": [
                            {
                                "script": KNOWN_SCRIPTS["restoration-biomass"],
                                "parametrization_dialogue": "LDMP.calculate_rest_biomass.DlgCalculateRestBiomassData",
                            }
                        ],
                    },
                    {
                        "id": "cb425356-09cf-4390-89dc-8542cdf0805c",
                        "name": tr_conf.tr(
                            "Table summarizing likely changes in biomass"
                        ),
                        "description": tr_conf.tr(
                            "Generate table summarizing potential change "
                            "in biomass due to restoration"
                        ),
                        "scripts": [
                            {
                                "script": KNOWN_SCRIPTS["change-biomass-summary-table"],
                                "parametrization_dialogue": "LDMP.calculate_rest_biomass.DlgCalculateRestBiomassSummaryTable",
                            }
                        ],
                    },
                ],
            },
        ],
    },
]


ALGORITHM_TREE = _load_algorithm_config(_ALGORITHM_CONFIG)
settings_manager = SettingsManager()
ADMIN_BOUNDS_KEY = download.get_admin_bounds()
CITIES = download.get_cities()
