"""
Code mapping for obsolete codes and aliases.

This module handles mapping of obsolete codes to current codes,
managing code aliases and label changes.

Validates: Requirements 13.2, 13.3
"""

from dataclasses import dataclass
from datetime import datetime
from typing import Dict, List, Optional
import json
import yaml
from pathlib import Path


@dataclass
class CodeMapping:
    """Mapping from obsolete code to current code."""
    
    obsolete_code: str
    current_code: str
    obsolete_label: str
    current_label: str
    effective_date: datetime
    reason: str  # "obsolete", "merged", "split", "renamed"
    notes: Optional[str] = None


class CodeMapper:
    """
    Manages mapping of obsolete codes to current codes.
    
    Validates: Requirements 13.2, 13.3
    """
    
    def __init__(self, mappings_dir: Optional[Path] = None):
        """
        Initialize the CodeMapper.
        
        Args:
            mappings_dir: Directory containing mapping files
        """
        self.mappings_dir = mappings_dir or Path("data/mappings")
        self.mappings_dir.mkdir(parents=True, exist_ok=True)
        
        # Mappings by referential type
        self.cim10_mappings: Dict[str, CodeMapping] = {}
        self.ccam_mappings: Dict[str, CodeMapping] = {}
        
        # Alias mappings (multiple codes pointing to same code)
        self.cim10_aliases: Dict[str, str] = {}
        self.ccam_aliases: Dict[str, str] = {}
        
        # Label change history
        self.label_changes: Dict[str, List[Dict]] = {}
    
    def load_mappings(
        self,
        referentiel_type: str,
        file_path: Path
    ) -> int:
        """
        Load code mappings from a file.
        
        Args:
            referentiel_type: "cim10" or "ccam"
            file_path: Path to mapping file (JSON or YAML)
        
        Returns:
            Number of mappings loaded
        
        Validates: Requirement 13.2
        """
        if not file_path.exists():
            raise FileNotFoundError(f"Mapping file not found: {file_path}")
        
        # Load file based on extension
        if file_path.suffix == ".json":
            with open(file_path, "r", encoding="utf-8") as f:
                data = json.load(f)
        elif file_path.suffix in [".yaml", ".yml"]:
            with open(file_path, "r", encoding="utf-8") as f:
                data = yaml.safe_load(f)
        else:
            raise ValueError(f"Unsupported file format: {file_path.suffix}")
        
        # Parse mappings
        count = 0
        for mapping_data in data.get("mappings", []):
            mapping = CodeMapping(
                obsolete_code=mapping_data["obsolete_code"],
                current_code=mapping_data["current_code"],
                obsolete_label=mapping_data["obsolete_label"],
                current_label=mapping_data["current_label"],
                effective_date=datetime.fromisoformat(mapping_data["effective_date"]),
                reason=mapping_data["reason"],
                notes=mapping_data.get("notes")
            )
            
            if referentiel_type == "cim10":
                self.cim10_mappings[mapping.obsolete_code] = mapping
            elif referentiel_type == "ccam":
                self.ccam_mappings[mapping.obsolete_code] = mapping
            
            count += 1
        
        # Load aliases
        for alias_data in data.get("aliases", []):
            alias_code = alias_data["alias"]
            canonical_code = alias_data["canonical"]
            
            if referentiel_type == "cim10":
                self.cim10_aliases[alias_code] = canonical_code
            elif referentiel_type == "ccam":
                self.ccam_aliases[alias_code] = canonical_code
        
        return count
    
    def map_code(
        self,
        code: str,
        referentiel_type: str
    ) -> Optional[str]:
        """
        Map an obsolete code to its current equivalent.
        
        Args:
            code: Code to map
            referentiel_type: "cim10" or "ccam"
        
        Returns:
            Current code if mapping exists, None otherwise
        
        Validates: Requirement 13.2
        """
        if referentiel_type == "cim10":
            # Check if it's an obsolete code
            if code in self.cim10_mappings:
                return self.cim10_mappings[code].current_code
            # Check if it's an alias
            if code in self.cim10_aliases:
                return self.cim10_aliases[code]
        
        elif referentiel_type == "ccam":
            # Check if it's an obsolete code
            if code in self.ccam_mappings:
                return self.ccam_mappings[code].current_code
            # Check if it's an alias
            if code in self.ccam_aliases:
                return self.ccam_aliases[code]
        
        # Code is current or unknown
        return None
    
    def get_mapping_info(
        self,
        code: str,
        referentiel_type: str
    ) -> Optional[CodeMapping]:
        """
        Get detailed mapping information for a code.
        
        Args:
            code: Code to look up
            referentiel_type: "cim10" or "ccam"
        
        Returns:
            CodeMapping if exists, None otherwise
        """
        if referentiel_type == "cim10":
            return self.cim10_mappings.get(code)
        elif referentiel_type == "ccam":
            return self.ccam_mappings.get(code)
        return None
    
    def is_obsolete(
        self,
        code: str,
        referentiel_type: str
    ) -> bool:
        """
        Check if a code is obsolete.
        
        Args:
            code: Code to check
            referentiel_type: "cim10" or "ccam"
        
        Returns:
            True if code is obsolete, False otherwise
        """
        if referentiel_type == "cim10":
            return code in self.cim10_mappings
        elif referentiel_type == "ccam":
            return code in self.ccam_mappings
        return False
    
    def add_mapping(
        self,
        mapping: CodeMapping,
        referentiel_type: str
    ) -> None:
        """
        Add a new code mapping.
        
        Args:
            mapping: CodeMapping to add
            referentiel_type: "cim10" or "ccam"
        
        Validates: Requirement 13.2
        """
        if referentiel_type == "cim10":
            self.cim10_mappings[mapping.obsolete_code] = mapping
        elif referentiel_type == "ccam":
            self.ccam_mappings[mapping.obsolete_code] = mapping
    
    def add_alias(
        self,
        alias_code: str,
        canonical_code: str,
        referentiel_type: str
    ) -> None:
        """
        Add a code alias.
        
        Args:
            alias_code: Alias code
            canonical_code: Canonical code
            referentiel_type: "cim10" or "ccam"
        
        Validates: Requirement 13.3
        """
        if referentiel_type == "cim10":
            self.cim10_aliases[alias_code] = canonical_code
        elif referentiel_type == "ccam":
            self.ccam_aliases[alias_code] = canonical_code
    
    def track_label_change(
        self,
        code: str,
        old_label: str,
        new_label: str,
        referentiel_type: str,
        effective_date: datetime
    ) -> None:
        """
        Track a label change for a code.
        
        Args:
            code: Code whose label changed
            old_label: Previous label
            new_label: New label
            referentiel_type: "cim10" or "ccam"
            effective_date: Date of change
        
        Validates: Requirement 13.3
        """
        key = f"{referentiel_type}:{code}"
        
        if key not in self.label_changes:
            self.label_changes[key] = []
        
        self.label_changes[key].append({
            "old_label": old_label,
            "new_label": new_label,
            "effective_date": effective_date.isoformat(),
            "referentiel_type": referentiel_type
        })
    
    def get_label_history(
        self,
        code: str,
        referentiel_type: str
    ) -> List[Dict]:
        """
        Get label change history for a code.
        
        Args:
            code: Code to look up
            referentiel_type: "cim10" or "ccam"
        
        Returns:
            List of label changes
        """
        key = f"{referentiel_type}:{code}"
        return self.label_changes.get(key, [])
    
    def save_mappings(
        self,
        referentiel_type: str,
        file_path: Path
    ) -> None:
        """
        Save mappings to a file.
        
        Args:
            referentiel_type: "cim10" or "ccam"
            file_path: Path to save file
        """
        # Select mappings and aliases
        if referentiel_type == "cim10":
            mappings = self.cim10_mappings
            aliases = self.cim10_aliases
        elif referentiel_type == "ccam":
            mappings = self.ccam_mappings
            aliases = self.ccam_aliases
        else:
            raise ValueError(f"Unknown referentiel type: {referentiel_type}")
        
        # Convert to serializable format
        data = {
            "referentiel_type": referentiel_type,
            "generated_at": datetime.now().isoformat(),
            "mappings": [
                {
                    "obsolete_code": m.obsolete_code,
                    "current_code": m.current_code,
                    "obsolete_label": m.obsolete_label,
                    "current_label": m.current_label,
                    "effective_date": m.effective_date.isoformat(),
                    "reason": m.reason,
                    "notes": m.notes
                }
                for m in mappings.values()
            ],
            "aliases": [
                {"alias": alias, "canonical": canonical}
                for alias, canonical in aliases.items()
            ]
        }
        
        # Save based on file extension
        if file_path.suffix == ".json":
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        elif file_path.suffix in [".yaml", ".yml"]:
            with open(file_path, "w", encoding="utf-8") as f:
                yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
        else:
            raise ValueError(f"Unsupported file format: {file_path.suffix}")
    
    def get_statistics(self, referentiel_type: str) -> Dict:
        """
        Get statistics about mappings.
        
        Args:
            referentiel_type: "cim10" or "ccam"
        
        Returns:
            Dictionary with statistics
        """
        if referentiel_type == "cim10":
            mappings = self.cim10_mappings
            aliases = self.cim10_aliases
        elif referentiel_type == "ccam":
            mappings = self.ccam_mappings
            aliases = self.ccam_aliases
        else:
            return {}
        
        # Count by reason
        reason_counts = {}
        for mapping in mappings.values():
            reason_counts[mapping.reason] = reason_counts.get(mapping.reason, 0) + 1
        
        return {
            "total_mappings": len(mappings),
            "total_aliases": len(aliases),
            "mappings_by_reason": reason_counts,
            "label_changes": len([k for k in self.label_changes.keys() if k.startswith(f"{referentiel_type}:")])
        }
