"""
Standardized parsing logic for Dr.Egeria v2 commands.
Implements the Attribute-First (Spec-Agnostic) parsing strategy.
"""
import re
from typing import Any, Dict, List, Optional
from loguru import logger

import pyegeria.core._globals as pyeg_globals
from md_processing.md_processing_utils.md_processing_constants import get_command_spec, load_commands
import md_processing.md_processing_utils.md_processing_constants as md_constants
from md_processing.md_processing_utils.common_md_utils import normalize_value
from .extraction import DrECommand
from .utils import parse_key_value

class AttributeFirstParser:
    """
    Parses a DrECommand by mapping its raw attributes to the canonical command specification.
    """
    def __init__(self, command: DrECommand, client: Optional[Any] = None, directive: str = "process"):
        self.command = command
        self.client = client
        self.directive = directive
        self.spec = get_command_spec(f"{command.verb} {command.object_type}")
        self.parsed_attributes = {}
        self.errors = []
        self.warnings = []

    def parse(self) -> Dict[str, Any]:
        if not self.spec:
            logger.error(f"No specification found for command: {self.command.verb} {self.command.object_type}")
            self.errors.append(f"No specification found for command: {self.command.verb} {self.command.object_type}")
            return {}

        # Spec can use "Attributes" or "attributes"
        spec_attrs = self.spec.get("Attributes", self.spec.get("attributes", []))
        logger.debug(f"Found {len(spec_attrs)} spec attributes for {self.command.verb} {self.command.object_type}")
        
        # Create a mapping of Label -> Spec Object
        label_map = {}
        for attr_obj in spec_attrs:
            if not isinstance(attr_obj, dict):
                continue
            
            # Check for flattened structure first (common in compact specs)
            if "name" in attr_obj and isinstance(attr_obj.get("variable_name"), str):
                self._add_to_label_map(label_map, attr_obj["name"], attr_obj)
            else:
                # Handle nested structure: {"canonical_name": {details}}
                for canonical_name, details in attr_obj.items():
                    if isinstance(details, dict):
                        self._add_to_label_map(label_map, canonical_name, details)

        logger.debug(f"Label map keys: {list(label_map.keys())}")

        # Map provided attributes
        for raw_label, raw_value in self.command.attributes.items():
            match = label_map.get(raw_label.lower())
            if not match:
                logger.warning(f"Unknown attribute label: '{raw_label}'")
                self.warnings.append(f"Unknown attribute label: '{raw_label}'")
                continue

            canonical_name, details = match
            # print(f"DEBUG: Processing {canonical_name}, style={details.get('style')}, value={raw_value}")
            pre_warnings = len(self.warnings)
            parsed_value = self._process_attribute_value(raw_value, details)
            if parsed_value is not None:
                # Use canonical_name for compatibility with legacy helpers (e.g. 'Description')
                self.parsed_attributes[canonical_name] = {
                    "value": parsed_value,
                    "valid": True,
                    "exists": True,
                    "status": "INFO" if len(self.warnings) == pre_warnings else "WARNING"
                }

        # Check for required attributes
        for attr_obj in spec_attrs:
            if not isinstance(attr_obj, dict):
                continue
            
            # Identify the canonical name and details
            if "name" in attr_obj and "variable_name" in attr_obj:
                # Flattened structure
                canonical_name = attr_obj["name"]
                details = attr_obj
            else:
                # Nested structure: {"canonical_name": {details}}
                canonical_items = list(attr_obj.items())
                if not canonical_items:
                    continue
                canonical_name, details = canonical_items[0]
                if not isinstance(details, dict):
                    continue

            if details.get("input_required", False) and canonical_name not in self.parsed_attributes:
                self.errors.append(f"Missing required attribute: '{canonical_name}'")

        qn_obj = self.parsed_attributes.get("Qualified Name", self.parsed_attributes.get("qualified_name"))
        qn_value = qn_obj.get("value") if isinstance(qn_obj, dict) else qn_obj

        return {
            "attributes": self.parsed_attributes,
            "valid": len(self.errors) == 0,
            "errors": self.errors,
            "warnings": self.warnings,
            "qualified_name": qn_value
        }

    def _add_to_label_map(self, label_map: Dict[str, Any], canonical_name: str, details: Dict[str, Any]):
        """Helper to index an attribute by its canonical name and all alternate labels."""
        label_map[canonical_name.lower()] = (canonical_name, details)
        alt_labels = details.get("attr_labels", "")
        if alt_labels:
            # Labels can be comma or semicolon separated
            for label in re.split(r'[,;]', str(alt_labels)):
                l = label.strip().lower()
                if l:
                    label_map[l] = (canonical_name, details)

    def _process_attribute_value(self, value: str, details: dict) -> Any:
        if value is None:
            return None
        value = value.strip()
        style = details.get("style", "Simple")
        
        if style == "Simple" or style == "ID" or style == "Reference Name":
            val = value
            if not val:
                # If it's a date-like attribute, return None to satisfy Pydantic/SDK
                name = details.get("name", "")
                if any(x in name for x in ["Time", "Date", "From", "To"]):
                    return None
            return val
            
        elif style in {"Boolean", "Bool"}:
            v = str(value).lower()
            return v in {"true", "yes", "1", "on"}
            
        elif style == "Dictionary" or style == "KeyValue":
            return parse_key_value(value)
            
        elif style in {"Enum", "Enumeration"}:
            v = value
            if not v:
                return v

            enum_type_name = details.get("enum_type")
            if not enum_type_name and details.get("name") == "Domain Identifier":
                enum_type_name = "GovernanceDomains"

            resolved_val = None
            if enum_type_name:
                # Look up in _globals or md_constants
                enum_obj = getattr(pyeg_globals, enum_type_name, None)
                if enum_obj is None:
                    enum_obj = getattr(md_constants, enum_type_name, None)

                if enum_obj is not None:
                    if isinstance(enum_obj, type) and issubclass(enum_obj, pyeg_globals.Enum):
                        resolved_val = pyeg_globals.resolve_enum(enum_obj, v)
                    elif isinstance(enum_obj, (list, set, tuple)):
                        v_norm = normalize_value(v)
                        for item in enum_obj:
                            if normalize_value(str(item)) == v_norm:
                                resolved_val = item
                                break

            if resolved_val is not None:
                return resolved_val

            # Fallback to hardcoded list in spec
            valid_values = details.get("valid_values", [])
            v_norm = normalize_value(v)
            for vv in valid_values:
                if normalize_value(vv) == v_norm:
                    return vv

            # Mismatch handling
            msg = f"Value '{v}' is not a valid enum value for '{enum_type_name or details.get('name', 'attribute')}'"
            if self.directive == "validate":
                self.warnings.append(msg)
            else:
                self.errors.append(msg)
            return v

        elif style in {"Valid Value", "ValidValue"}:
            v = value
            if not v:
                return v

            # 1. Try dynamic lookup if client is available
            if self.client and hasattr(self.client, "get_valid_metadata_values"):
                prop_name = details.get("property_name") or details.get("name")
                type_name = details.get("type_name")

                try:
                    valid_elements = self.client.get_valid_metadata_values(prop_name, type_name)
                    if isinstance(valid_elements, list) and len(valid_elements) > 0:
                        v_norm = normalize_value(v)
                        for el in valid_elements:
                            pref_val = el.get("preferredValue")
                            disp_name = el.get("displayName")

                            if (pref_val and normalize_value(pref_val) == v_norm) or \
                               (disp_name and normalize_value(disp_name) == v_norm):
                                data_type = el.get("dataType", "string").lower()
                                if data_type in ["int", "integer"] and pref_val is not None:
                                    try:
                                        return int(pref_val)
                                    except (ValueError, TypeError):
                                        return pref_val
                                return pref_val

                except Exception as e:
                    logger.debug(f"Dynamic valid value lookup failed for {prop_name}: {e}")

            # 2. Fallback to hardcoded list in spec
            valid_values = details.get("valid_values", [])
            v_norm = normalize_value(v)
            for vv in valid_values:
                if normalize_value(vv) == v_norm:
                    return vv

            # Mismatch handling
            msg = f"Value '{v}' is not a valid metadata value for '{details.get('name', 'attribute')}'"
            if self.directive == "validate":
                self.warnings.append(msg)
            else:
                self.errors.append(msg)
            return v
            
        elif style in {"List", "NameList", "Simple List", "Reference Name List"}:
            return [v.strip() for v in re.split(r'[,\n]', value) if v.strip()]
            
        return value

def parse_dr_egeria_content(text: str) -> List[Dict[str, Any]]:
    """Helper to extract and parse all commands in one go."""
    from .extraction import UniversalExtractor
    extractor = UniversalExtractor(text)
    commands = extractor.extract_commands()
    
    results = []
    for cmd in commands:
        if not cmd.is_command:
            continue
            
        parser = AttributeFirstParser(cmd)
        ir = parser.parse()
        results.append({
            "verb": cmd.verb,
            "object_type": cmd.object_type,
            "attributes": ir["attributes"],
            "errors": ir["errors"],
            "warnings": ir["warnings"],
            "raw_block": cmd.raw_block
        })
    return results
