import json
import logging
import typing
from dataclasses import dataclass, asdict
from enum import Enum
from typing import Union
from qgis.PyQt.QtXml import QDomDocument
from qgis.core import QgsFeatureRenderer, QgsReadWriteContext
from qgis.PyQt.QtCore import QSettings
from SAGisXPlanung.XPlan.types import GeometryType
logger = logging.getLogger(__name__)

class ConfigSaveException(Exception):
    """Exception for cases, where values could not be written to the config entry"""
    pass

class GeometryCorrectionMethod(Enum):
    PreserveTopology = 1
    RigorousRemoval = 2

@dataclass
class GeometryValidationConfig:
    correct_geometries: bool
    correct_method: GeometryCorrectionMethod

@dataclass
class XPlanung24Account:
    name: str
    api_key: str

    def to_dict(self):
        return asdict(self)

    @staticmethod
    def from_dict(data):
        return XPlanung24Account(**data)

class QgsConfig:
    STYLES = 'plugins/xplanung/styles'
    CONNECTION = 'plugins/xplanung/connection'
    CORRECT_GEOMETRIES = 'plugins/xplanung/correct_geometries'
    CORRECT_GEOMETRIES_METHOD = 'plugins/xplanung/correct_geometries_method'
    NEXUS_SETTINGS = 'plugins/xplanung/nexus/settings'
    LAST_EXPORT_PATH = 'plugins/xplanung/last_export_dir'
    XPLAN24_ACCOUNT = 'plugins/xplanung/xplan24_account'

    @staticmethod
    def remove_section(settings_key: str):
        qs = QSettings()
        qs.remove(f'{settings_key}/')

    @staticmethod
    def class_renderer(xplan_class: type, geometry_type: GeometryType) -> Union[None, QgsFeatureRenderer]:
        if geometry_type is None:
            return
        qs = QSettings()
        xml = qs.value(f'{QgsConfig.STYLES}/{xplan_class.__name__}/{geometry_type}/renderer', None)
        if xml is None:
            return
        doc = QDomDocument()
        success, error_message, error_line, error_column = doc.setContent(xml)
        if success:
            return QgsFeatureRenderer.load(doc.firstChild().toElement(), QgsReadWriteContext())

    @staticmethod
    def set_class_renderer(xplan_class: type, geometry_type: GeometryType, renderer: QgsFeatureRenderer):
        qs = QSettings()
        doc = QDomDocument()
        elem = renderer.save(doc, QgsReadWriteContext())
        doc.appendChild(elem)
        if doc.isNull():
            raise ConfigSaveException('Document is empty. Renderer failed to save.')
        qs.setValue(f'{QgsConfig.STYLES}/{xplan_class.__name__}/{geometry_type}/renderer', doc.toString())

    @staticmethod
    def layer_priority(xplan_class: Union[type, str], geometry_type: GeometryType) -> Union[None, int]:
        if geometry_type is None:
            return
        if not isinstance(xplan_class, str):
            xplan_class = xplan_class.__name__
        qs = QSettings()
        prio = qs.value(f'{QgsConfig.STYLES}/{xplan_class}/{geometry_type}/layer_prio', None)
        if prio is None:
            return
        return prio

    @staticmethod
    def set_layer_priority(xplan_class: Union[type, str], geometry_type: GeometryType, layer_priority: int):
        if not isinstance(xplan_class, str):
            xplan_class = xplan_class.__name__
        qs = QSettings()
        qs.setValue(f'{QgsConfig.STYLES}/{xplan_class}/{geometry_type}/layer_prio', layer_priority)

    @staticmethod
    def connection_params() -> typing.Dict:
        qs = QSettings()
        conn_name = qs.value(QgsConfig.CONNECTION)
        return {'username': qs.value(f'PostgreSQL/connections/{conn_name}/username'), 'password': qs.value(f'PostgreSQL/connections/{conn_name}/password'), 'host': qs.value(f'PostgreSQL/connections/{conn_name}/host'), 'port': qs.value(f'PostgreSQL/connections/{conn_name}/port'), 'db': qs.value(f'PostgreSQL/connections/{conn_name}/database'), 'service': qs.value(f'PostgreSQL/connections/{conn_name}/service')}

    @staticmethod
    def geometry_validation_config() -> GeometryValidationConfig:
        qs = QSettings()
        return GeometryValidationConfig(correct_geometries=bool(int(qs.value(QgsConfig.CORRECT_GEOMETRIES, 1))), correct_method=GeometryCorrectionMethod(int(qs.value(QgsConfig.CORRECT_GEOMETRIES_METHOD, 1))))

    @staticmethod
    def set_geometry_validation_config(config: GeometryValidationConfig):
        qs = QSettings()
        qs.setValue(QgsConfig.CORRECT_GEOMETRIES, int(config.correct_geometries))
        qs.setValue(QgsConfig.CORRECT_GEOMETRIES_METHOD, config.correct_method.value)

    @staticmethod
    def nexus_settings() -> str:
        qs = QSettings()
        return qs.value(QgsConfig.NEXUS_SETTINGS, None)

    @staticmethod
    def set_nexus_settings(config_json: str):
        qs = QSettings()
        qs.setValue(QgsConfig.NEXUS_SETTINGS, config_json)

    @staticmethod
    def last_export_directory() -> str:
        qs = QSettings()
        return qs.value(QgsConfig.LAST_EXPORT_PATH, '')

    @staticmethod
    def set_last_export_directory(directory: str):
        qs = QSettings()
        qs.setValue(QgsConfig.LAST_EXPORT_PATH, directory)

    @staticmethod
    def xplan24_accounts() -> typing.List[XPlanung24Account]:
        qs = QSettings()
        raw = qs.value(QgsConfig.XPLAN24_ACCOUNT, '')
        if not raw:
            return []
        try:
            data = json.loads(raw)
            return [XPlanung24Account.from_dict(item) for item in data]
        except Exception as e:
            logger.error(e)
            return []

    @staticmethod
    def set_xplan24_accounts(account_data: typing.List[XPlanung24Account]):
        qs = QSettings()
        data = [account.to_dict() for account in account_data]
        qs.setValue(QgsConfig.XPLAN24_ACCOUNT, json.dumps(data))