"""Persistent config storage for admin targets, breakevens, GP thresholds."""
import copy
import json
import os
import logging
from pathlib import Path
from typing import Dict, Optional
from dotenv import load_dotenv

_ROOT = Path(__file__).resolve().parent.parent.parent.parent
_env = _ROOT / ".env"
if _env.exists():
    load_dotenv(_env)
else:
    load_dotenv()

logger = logging.getLogger(__name__)

# Configuration storage path - use Docker volume if available, fallback to local
_default_storage_dir = os.getenv("CONFIG_STORAGE_DIR", "/app/data/config")
# If /app doesn't exist (not in Docker), use a local path
if not Path("/app").exists() and _default_storage_dir.startswith("/app"):
    _default_storage_dir = os.path.join(os.path.dirname(__file__), "../../../data/config")

CONFIG_STORAGE_DIR = Path(_default_storage_dir)
CONFIG_STORAGE_FILE = CONFIG_STORAGE_DIR / "admin_config.json"

# Ensure directory exists (only if parent directories exist or we can create them)
try:
    CONFIG_STORAGE_DIR.mkdir(parents=True, exist_ok=True)
except (PermissionError, OSError) as e:
    # If we can't create the directory, we'll handle it in the class
    logger.warning(f"Could not create config storage directory {CONFIG_STORAGE_DIR}: {e}")

# Default GP color thresholds based on department types
DEFAULT_GP_THRESHOLDS = {
    "electrical": {
        "green_min": 55.0,
        "yellow_min": 40.0,
        "yellow_max": 55.0,
        "red_max": 40.0
    },
    "commercial": {
        "green_min": 55.0,
        "yellow_min": 40.0,
        "yellow_max": 55.0,
        "red_max": 40.0
    },
    "air": {
        "green_min": 40.0,
        "yellow_min": 35.0,
        "yellow_max": 40.0,
        "red_max": 35.0
    },
    "solar": {
        "green_min": 40.0,
        "yellow_min": 35.0,
        "yellow_max": 40.0,
        "red_max": 35.0
    },
    "residential": {
        "green_min": 55.0,
        "yellow_min": 40.0,
        "yellow_max": 55.0,
        "red_max": 40.0
    }
}


# Default KPI targets per branch/department
DEFAULT_KPI_TARGETS = {
    "Busselton": {
        "Electrical": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        },
        "Air": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        }
    },
    "Bunbury": {
        "Residential": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        },
        "Air": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        },
        "Solar": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        },
        "Commercial": {
            "jobsCompleted": {"target": 720, "unit": "jobs", "weight": 35, "title": "Jobs Completed", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 4, "unit": "days", "weight": 10, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "pendingJobs": {"target": 75, "unit": "jobs", "weight": 30, "title": "Total Pending Jobs", "lowerIsBetter": True, "aggregation": "average"},
            "workloadForm": {"target": 100, "unit": "%", "weight": 5, "title": "Weekly Workload Form Completion", "lowerIsBetter": False, "aggregation": "average"},
            "dueDates": {"target": 100, "unit": "%", "weight": 15, "title": "Pending Jobs with Due Dates", "lowerIsBetter": False, "aggregation": "average"}
        }
    },
    "Mandurah": {
        "Electrical": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        },
        "Air": {
            "quoteConversion": {"target": 60, "unit": "%", "weight": 30, "title": "Quote Conversion", "lowerIsBetter": False, "aggregation": "average"},
            "clientReviews": {"target": 30, "unit": "reviews", "weight": 10, "title": "Client Reviews", "lowerIsBetter": False, "aggregation": "sum"},
            "firstAttendance": {"target": 6.5, "unit": "days", "weight": 25, "title": "Avg. Time to First Attendance", "lowerIsBetter": True, "aggregation": "average"},
            "quoteAcceptance": {"target": 6, "unit": "days", "weight": 25, "title": "Quote Creation to Acceptance Time", "lowerIsBetter": True, "aggregation": "average"}
        }
    }
}


class ConfigStorage:
    """Manages persistent storage of admin configuration."""
    
    def __init__(self, storage_file: Path = CONFIG_STORAGE_FILE):
        self.storage_file = storage_file
        self._ensure_storage_file()
    
    def _ensure_storage_file(self):
        """Ensure the storage file exists with default structure."""
        # Try to ensure directory exists
        try:
            self.storage_file.parent.mkdir(parents=True, exist_ok=True)
        except (PermissionError, OSError):
            # If we can't create the directory, we'll use in-memory only
            pass
        
        if not self.storage_file.exists():
            try:
                default_config = self._get_default_config()
                self._write_config(default_config)
            except (PermissionError, OSError, IOError):
                # If we can't write, that's okay - we'll use defaults
                logger.warning(f"Could not create config file {self.storage_file}. Using defaults only.")
    
    def _get_default_config(self) -> Dict:
        """Get default configuration structure."""
        return {
            "targets_breakevens": {},
            "gp_thresholds": DEFAULT_GP_THRESHOLDS.copy(),
            "kpi_targets": {}
        }
    
    def _read_config(self) -> Dict:
        """Read configuration from storage file."""
        try:
            if self.storage_file.exists():
                with open(self.storage_file, 'r') as f:
                    config = json.load(f)
                    # Ensure all required keys exist
                    if "targets_breakevens" not in config:
                        config["targets_breakevens"] = {}
                    if "gp_thresholds" not in config:
                        config["gp_thresholds"] = DEFAULT_GP_THRESHOLDS.copy()
                    if "kpi_targets" not in config:
                        config["kpi_targets"] = {}
                    return config
        except (json.JSONDecodeError, IOError) as e:
            logger.warning(f"Error reading config storage: {e}. Using defaults.")
        
        return self._get_default_config()
    
    def _write_config(self, config: Dict):
        """Write configuration to storage file."""
        try:
            # Ensure directory exists
            self.storage_file.parent.mkdir(parents=True, exist_ok=True)
            
            # Write to temporary file first, then rename (atomic operation)
            temp_file = self.storage_file.with_suffix('.tmp')
            with open(temp_file, 'w') as f:
                json.dump(config, f, indent=2)
            temp_file.replace(self.storage_file)
            logger.debug(f"Configuration written to {self.storage_file}")
        except (IOError, PermissionError, OSError) as e:
            logger.warning(f"Could not write config storage to {self.storage_file}: {e}. Changes will not persist.")
            # Don't raise - allow in-memory operation
    
    def get_targets_breakevens(self) -> Dict:
        """Get all targets and breakevens."""
        config = self._read_config()
        return config.get("targets_breakevens", {})
    
    def update_target_breakeven(
        self, 
        branch_id: str, 
        company_id: int, 
        target: Optional[int] = None,
        breakeven: Optional[int] = None
    ):
        """
        Update target and/or breakeven for a specific branch/company.
        
        Args:
            branch_id: Branch identifier (branch1, branch2, branch3)
            company_id: Company/department ID
            target: New target value (optional)
            breakeven: New breakeven value (optional)
        """
        config = self._read_config()
        
        if "targets_breakevens" not in config:
            config["targets_breakevens"] = {}
        
        key = f"{branch_id}_{company_id}"
        
        if key not in config["targets_breakevens"]:
            config["targets_breakevens"][key] = {}
        
        if target is not None:
            config["targets_breakevens"][key]["target"] = target
        if breakeven is not None:
            config["targets_breakevens"][key]["breakeven"] = breakeven
        
        self._write_config(config)
        logger.info(f"Updated target/breakeven for {branch_id} company {company_id}")
    
    def get_gp_thresholds(self) -> Dict:
        """Get all GP color thresholds."""
        config = self._read_config()
        return config.get("gp_thresholds", DEFAULT_GP_THRESHOLDS.copy())
    
    def update_gp_thresholds(self, department_type: str, thresholds: Dict):
        """
        Update GP color thresholds for a department type.
        
        Args:
            department_type: Department type (electrical, commercial, air, solar, residential)
            thresholds: Dictionary with green_min, yellow_min, yellow_max, red_max
        """
        config = self._read_config()
        
        if "gp_thresholds" not in config:
            config["gp_thresholds"] = DEFAULT_GP_THRESHOLDS.copy()
        
        config["gp_thresholds"][department_type.lower()] = thresholds
        self._write_config(config)
        logger.info(f"Updated GP thresholds for {department_type}")
    
    @staticmethod
    def _deep_merge(base: Dict, overrides: Dict) -> Dict:
        """
        Deep merge two dicts. Values in *overrides* take priority.
        Returns a new dict (does not mutate inputs).
        """
        result = copy.deepcopy(base)
        for key, value in overrides.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = ConfigStorage._deep_merge(result[key], value)
            else:
                result[key] = copy.deepcopy(value)
        return result

    def get_kpi_targets(self) -> Dict:
        """
        Get all KPI targets: defaults deep-merged with admin overrides.
        Admin overrides (stored in the config file) take priority over DEFAULT_KPI_TARGETS.
        """
        config = self._read_config()
        admin_overrides = config.get("kpi_targets", {})
        return self._deep_merge(DEFAULT_KPI_TARGETS, admin_overrides)

    def update_kpi_targets(self, branch: str, department: str, metric_key: str, updates: Dict):
        """
        Update a specific KPI target metric for a branch/department.

        Args:
            branch: Branch name (e.g. "Busselton", "Bunbury", "Mandurah")
            department: Department name (e.g. "Electrical", "Air", "Commercial")
            metric_key: Metric key (e.g. "quoteConversion", "clientReviews")
            updates: Dict of fields to update (e.g. {"target": 70, "weight": 25})
        """
        config = self._read_config()

        kpi = config.setdefault("kpi_targets", {})
        branch_dict = kpi.setdefault(branch, {})
        dept_dict = branch_dict.setdefault(department, {})
        metric_dict = dept_dict.setdefault(metric_key, {})
        metric_dict.update(updates)

        self._write_config(config)
        logger.info(f"Updated KPI target {branch}/{department}/{metric_key}: {updates}")

    def update_all_kpi_targets(self, targets: Dict):
        """
        Bulk-replace the entire kpi_targets override dict.

        Args:
            targets: Full kpi_targets dict
                     { branch: { department: { metricKey: { target: N, ... } } } }
        """
        config = self._read_config()
        config["kpi_targets"] = targets
        self._write_config(config)
        logger.info("Bulk-updated all KPI targets")

    def get_gp_color(self, department_name: str, gp_margin: float) -> str:
        """
        Get color for a GP margin based on department type.
        
        Args:
            department_name: Department name (e.g., "Electrical", "Air", "Solar")
            gp_margin: Gross profit margin percentage (0-100)
        
        Returns:
            Color string: "green", "yellow", or "red"
        """
        thresholds = self.get_gp_thresholds()
        
        # Map department name to type
        dept_lower = department_name.lower()
        if "electrical" in dept_lower or "residential" in dept_lower:
            dept_type = "electrical" if "electrical" in dept_lower else "residential"
        elif "commercial" in dept_lower:
            dept_type = "commercial"
        elif "air" in dept_lower:
            dept_type = "air"
        elif "solar" in dept_lower:
            dept_type = "solar"
        else:
            # Default to electrical/residential thresholds
            dept_type = "electrical"
        
        dept_thresholds = thresholds.get(dept_type, DEFAULT_GP_THRESHOLDS.get(dept_type, {}))
        
        green_min = dept_thresholds.get("green_min", 55.0)
        yellow_min = dept_thresholds.get("yellow_min", 40.0)
        yellow_max = dept_thresholds.get("yellow_max", 55.0)
        
        if gp_margin >= green_min:
            return "green"
        elif yellow_min <= gp_margin < yellow_max:
            return "yellow"
        else:
            return "red"


# Global instance (ConfigStorage or DBConfigStorage when using MySQL)
_config_storage = None

def get_config_storage():
    """Get the global config storage instance. Uses Django client-scoped DB when available."""
    global _config_storage
    if _config_storage is None:
        if os.getenv("DJANGO_SETTINGS_MODULE"):
            from app.config.django_config_storage import DjangoConfigStorage
            _config_storage = DjangoConfigStorage()
        else:
            _url = os.getenv("DATABASE_URL", "")
            if _url and "mysql" in _url:
                from app.config.db_config_storage import DBConfigStorage
                _config_storage = DBConfigStorage()
            else:
                _config_storage = ConfigStorage()
    return _config_storage
