import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional, Union

from arkeo.path import PathManager
from utils.json import load_json


log = logging.getLogger(__name__)


KEY_CUSTOM = "custom"
KEY_DEFAULT = "default"
KEY_MERGED = "merged"
KEY_META = "_meta"

FILE_EXT = "json"


class ConfigurationManager:
    """manages configuratons stored as JSON"""

    def __init__(
        self,
        path_manager: PathManager,
        custom_configs: Optional[Union[str, Dict[str, Any], Path]] = None,
    ):
        self.path = path_manager
        self.data: Dict[str, Any] = {}

        self._load_data(custom_configs)

    @property
    def dir_data(self) -> str:
        return self.path.config

    @property
    def default_path(self) -> str:
        return f"{self.dir_data}parse-{KEY_DEFAULT}.{FILE_EXT}"

    def _load_data(self, source: Optional[Union[str, Path, Dict[str, Any]]]) -> None:
        """load configurations from file or dict"""
        if isinstance(source, dict):
            self.data = source
            return

        if source is None:
            custom_path = self.default_path.replace(KEY_DEFAULT, KEY_CUSTOM)
            source = custom_path

        # Handle str/Path case
        if isinstance(source, (str, Path)):
            default = self._load_json(self.default_path)
            custom = self._load_json(source)
            self.data = self._merge_data(default, custom)
            return

        raise TypeError(f"requires str, dict, or None; got {type(source)}")

    def _load_json(self, file_path: Union[str, Path]) -> Dict[str, Any]:
        """load json file with error handling"""
        return load_json(self.path.drive, file_path) or {KEY_DEFAULT: {}}

    def _merge_data(
        self, default: Dict[str, Any], custom: Dict[str, Any]
    ) -> Dict[str, Any]:
        """merge default and custom json, preserving both _meta sections"""
        merged = {**default, **custom}

        # special handling for _meta to combine both sections
        if KEY_META in default and KEY_META in custom:
            merged[KEY_META] = {**default[KEY_META], **custom[KEY_META]}

        return merged

    def get(self, key: str) -> Dict[str, Any]:
        """get config for key, merging with default if needed"""
        if key not in self.data:
            return self.data[KEY_DEFAULT]

        if KEY_MERGED not in self.data[key]:
            self._merge_key(key)

        return self.data[key]

    def sort(self) -> None:
        """
        data_name.yymmdd.bak      # full backup
        data_name.del             # partial update (unprocessed)
        data_name.yymmddhhmm.del  # partial update (processed)
        """
        today = datetime.now().strftime("%y%m%d")
        timestamp = datetime.now().strftime("%y%m%d%H%M")

        # build paths
        data_name = f"parse-{KEY_CUSTOM}"
        data_path = f"{self.dir_data}{data_name}.{FILE_EXT}"

        # load and validate data
        cfgs = json.loads(self.path.drive.read(data_path))

        # file operations with better error handling
        try:
            # create backup if it doesn't exist for today
            backup_file = f"{self.dir_data}{data_name}.{today}.bak"
            if not self.path.drive.file_exists(backup_file):
                if self.path.drive.file_exists(data_path):
                    self.path.drive.rename(data_path, backup_file)
                else:
                    log.warning(f"main data file not found: {data_path}")
                    return False

            # write updated data
            self.path.drive.write(
                data_path, json.dumps(dict(sorted(self.data.items())))
            )

        except Exception as e:
            log.error(f"failed to update directory: {e}")
            return False

        return True

    def _merge_key(self, key: str) -> None:
        """merge default with custom and mark as merged"""
        default = self.data[KEY_DEFAULT]
        entity = self.data[key]
        self.data[key] = self._prepend_value(default, entity)
        self.data[key][KEY_MERGED] = True

    def _prepend_value(
        self, base: Dict[str, Any], overlay: Dict[str, Any]
    ) -> Dict[str, Any]:
        """merge custom overlay onto default base (overlay takes precedence)"""
        result = base.copy()

        for k, v in overlay.items():
            if k not in result:
                result[k] = v
            elif isinstance(v, dict) and isinstance(result[k], dict):
                result[k] = self._prepend_value(result[k], v)
            elif isinstance(v, list) and isinstance(result[k], list):
                result[k] = v + result[k]
            else:
                result[k] = v

        return result
