import ast
import json
import secrets
from dataclasses import dataclass
from pathlib import Path

import pandas as pd

from openlog.__about__ import DIR_PLUGIN_ROOT
from openlog.datamodel.assay.generic_assay import GenericAssay
from openlog.gui.assay_visualization.categorical_symbology import CategoricalSymbology


@dataclass
class BarSymbology(CategoricalSymbology):
    # Alias that can be used to define svg pattern file path
    PLUGIN_ROOT_ALIAS = "{plugin_root}"
    USGS_SYMB_ROOT_ALIAS = "{usgs_symbology}"

    # Value for aliases
    USGS_SYMB_ROOT = "{plugin_root}/resources/usgs_symbology"

    def __init__(self) -> None:
        super().__init__()
        self.pattern_col = ""
        self.scale_col = ""
        self.pattern_map = {}
        self.scale_map = {}

    @classmethod
    def from_json(cls, data):
        """
        Define BarSymbology from json data (see to_json)

        Args:
            data: json data

        Returns: BarSymbology

        """
        res = cls()
        res.pattern_col = data["pattern_col"]
        res.scale_col = data["scale_col"]
        res.color_col = data["color_col"]

        for k, v in data["pattern_map"].items():
            res.pattern_map[k] = str(v)
        for k, v in data["scale_map"].items():
            res.scale_map[k] = int(v)
        for k, v in data["color_map"].items():
            res.color_map[k] = str(v)

        return res

    def to_dict(self) -> dict:
        """
        Convert configuration to dict (use for json export)

        Returns:
            dict: configuration as dict

        """
        result = {}
        result["pattern_col"] = self.pattern_col
        result["scale_col"] = self.scale_col
        result["pattern_map"] = self.pattern_map
        result["scale_map"] = self.scale_map
        result = result | super().to_dict()

        return result

    def merge(self, other, replace: bool = True) -> None:
        """
        Merge map from a BarSymbology

        Args:
            other: (BarSymbology)
        """

        self.pattern_col = other.pattern_col
        self.scale_col = other.scale_col
        pattern_map_lower = self._set_lowercase_keys(self.pattern_map)
        scale_map_lower = self._set_lowercase_keys(self.scale_map)

        for key, val in other.pattern_map.items():
            if key.lower() not in pattern_map_lower or replace:
                self.pattern_map[key] = val
        for key, val in other.scale_map.items():
            if key.lower() not in scale_map_lower or replace:
                self.scale_map[key] = val

        super().merge(other, replace)

    def replace_alias_in_pattern(self, pattern: str) -> str:
        """
        Replace alias in pattern string to get full path

        Args:
            pattern: (str) pattern with alias

        Returns: patern in aliases replaced

        """
        pattern = pattern.replace(self.USGS_SYMB_ROOT_ALIAS, self.USGS_SYMB_ROOT)
        pattern = pattern.replace(self.PLUGIN_ROOT_ALIAS, str(DIR_PLUGIN_ROOT))
        return pattern

    def add_alias_into_pattern(self, pattern: str) -> str:
        """
        Add alias into pattern to reduce pattern size

        Args:
            pattern: pattern with full path

        Returns: pattern with aliases

        """
        pattern = pattern.replace(str(DIR_PLUGIN_ROOT), self.PLUGIN_ROOT_ALIAS)
        pattern = pattern.replace(self.USGS_SYMB_ROOT, self.USGS_SYMB_ROOT_ALIAS)
        return pattern

    def add_pattern(self, key: str, pattern: str = None) -> None:
        """
        Add a key symbology to map (if no symbology defined get_random_symbology used)

        Args:
            key: (str) key
            symbology: (BarSymbology) symbology (default : None)
        """
        if pattern is None:
            self.pattern_map[key] = self._get_random_pattern()
        else:
            self.pattern_map[key] = pattern

    def get_pattern_file(self, key: str) -> str:
        """
        Get pattern file with alias conversion

        Args:
            key: (str) key

        Returns: (str) pattern file

        """
        key = key.lower()
        pattern_map_lower = self._set_lowercase_keys(self.pattern_map)

        if key in pattern_map_lower:
            pattern_file = pattern_map_lower[key]
        else:
            # Random usgs pattern (check if available)
            pattern_file = self._get_random_pattern()
            while not Path(self.replace_alias_in_pattern(pattern_file)).exists():
                pattern_file = self._get_random_pattern()
        svg_file = self.replace_alias_in_pattern(pattern_file)
        return svg_file

    def add_scale(self, key: str, scale: int = None) -> None:
        """
        Add a key symbology to map (if no symbology defined get_random_symbology used)

        Args:
            key: (str) key
            scale: (int) symbology (default : None)
        """
        if scale is None:
            self.scale_map[key] = 200
        else:
            self.scale_map[key] = scale

    def get_scale(self, key: str) -> int:
        """
        Get scale

        Args:
            key: (str) key

        Returns: (int) scale

        """
        key = key.lower()
        scale_map_lower = self._set_lowercase_keys(self.scale_map)
        if key in scale_map_lower:
            scale = scale_map_lower[key]
        else:
            scale = 200
        return scale

    def _get_random_pattern(self) -> str:
        usgs_code = secrets.choice(range(601, 733))
        return self.USGS_SYMB_ROOT_ALIAS + f"/usgs{usgs_code}.svg"

    def init_from_database(self, session, assay: GenericAssay):
        """
        Specific for Geotic connection.
        Colors are stored in tblCodeAlphaDetail :
            - integer : autocad color index
            - rgb tuple
        """

        self.init_from_assay(assay)

        # read autocad index colors
        colors = pd.read_csv(
            str(DIR_PLUGIN_ROOT / "resources" / "geotic_color" / "geotic_color.csv"),
            index_col="index",
        )
        # convert to dictionnary : index : hex
        colors = {
            str(key - 1): "#%02x%02x%02x" % tuple(rgb.to_list())
            for key, rgb in colors.iterrows()
        }

        self.color_map.clear()
        result = session.execute(
            "SELECT Filtre, Couleur FROM dbo.tblCodeAlphaDetail;"
        ).fetchall()
        for key, color in result:
            key = str(key).lower()
            if colors.get(str(color)):
                self.color_map[key] = colors[str(color)]
            # color can be a rgb tuple
            elif (
                str(color).count(",") == 2
                and str(color).count("(") == 1
                and str(color).count(")") == 1
            ):
                try:
                    self.color_map[key] = "#%02x%02x%02x" % ast.literal_eval(str(color))
                except:
                    continue

        val_dict = {}

        if self.color_col and self.color_col not in val_dict:
            y = assay.get_distinct_col_values(self.color_col)
            val_dict[self.color_col] = y

        if self.color_col:
            for category in val_dict[self.color_col]:
                category = str(category)
                category_lower = category.lower()
                if category not in self.color_map:
                    if category_lower not in self.color_map:
                        self.add_color(category)
                    else:
                        self.add_color(
                            key=category, color=self.color_map.get(category_lower)
                        )

        # constant pattern
        self.pattern_map.clear()
        if self.pattern_col:
            for category in val_dict[self.pattern_col]:
                category = str(category)
                if category not in self.pattern_map:
                    self.add_pattern(category, pattern="697")

    def init_from_assay(self, assay: GenericAssay) -> None:
        # Get all values from assay
        super().init_from_assay(assay)
        val_dict = {}
        if self.pattern_col and self.pattern_col not in val_dict:
            y = assay.get_distinct_col_values(self.pattern_col)
            val_dict[self.pattern_col] = y

        if self.scale_col and self.scale_col not in val_dict:
            y = assay.get_distinct_col_values(self.scale_col)
            val_dict[self.scale_col] = y

        self.pattern_map.clear()
        if self.pattern_col:
            for category in val_dict[self.pattern_col]:
                if category not in self.pattern_map:
                    self.add_pattern(str(category))

        self.scale_map.clear()
        if self.scale_col:
            for category in val_dict[self.scale_col]:
                if category not in self.scale_map:
                    self.add_scale(str(category))
