"""Mart configuration generator.

Creates, validates, and exports MartConfig objects that drive
the 4-object DDL pipeline.
"""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from .types import (
    DynamicColumnMapping,
    JoinPattern,
    MartConfig,
    MartValidationResult,
    QualityIssueLevel,
    ReportType,
    ValidationMessage,
)

logger = logging.getLogger(__name__)


class MartConfigGenerator:
    """In-memory store and factory for MartConfig objects."""

    def __init__(self) -> None:
        self._configs: Dict[str, MartConfig] = {}

    # ------------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------------

    def create_config(
        self,
        project_name: str,
        report_type: str = "P&L",
        hierarchy_table: str = "",
        mapping_table: str = "",
        account_segment: str = "GROSS",
        measure_prefix: str = "AMT_",
        schema_name: str = "ANALYTICS",
        description: str = "",
    ) -> MartConfig:
        """Create a new mart config with the 7-variable contract.

        Args:
            project_name: Unique identifier for this mart project.
            report_type: One of P&L, Balance Sheet, LOS, Custom.
            hierarchy_table: Source hierarchy table/view name.
            mapping_table: ID_SOURCE mapping table/view name.
            account_segment: GROSS or NET filter value.
            measure_prefix: Column prefix for measures (e.g. AMT_).
            schema_name: Target schema name.
            description: Optional human-readable description.

        Returns:
            The created MartConfig.
        """
        rt = ReportType.CUSTOM
        for member in ReportType:
            if member.value.upper() == report_type.upper() or member.name.upper() == report_type.upper():
                rt = member
                break

        config = MartConfig(
            project_name=project_name,
            report_type=rt,
            hierarchy_table=hierarchy_table,
            mapping_table=mapping_table,
            account_segment=account_segment,
            measure_prefix=measure_prefix,
            schema_name=schema_name,
            description=description or f"Data mart for {project_name}",
        )
        self._configs[project_name] = config
        logger.info("Created mart config: %s", project_name)
        return config

    def list_configs(self) -> List[str]:
        """Return all stored config project names."""
        return list(self._configs.keys())

    def get_config(self, project_name: str) -> Optional[MartConfig]:
        """Retrieve a config by project name."""
        return self._configs.get(project_name)

    def delete_config(self, project_name: str) -> bool:
        """Remove a config from the store."""
        return self._configs.pop(project_name, None) is not None

    # ------------------------------------------------------------------
    # Mutators
    # ------------------------------------------------------------------

    def add_join_pattern(
        self,
        project_name: str,
        name: str,
        join_keys: List[str],
        fact_keys: List[str],
        source_table: str = "",
        filter_expr: Optional[str] = None,
    ) -> MartConfig:
        """Add a UNION ALL branch to an existing config.

        Args:
            project_name: Target config.
            name: Branch identifier (e.g. 'revenue').
            join_keys: Columns for hierarchy dimension joins.
            fact_keys: Measure columns to aggregate.
            source_table: Source table name.
            filter_expr: Optional WHERE clause fragment.

        Returns:
            Updated MartConfig.

        Raises:
            KeyError: If project_name not found.
        """
        config = self._require(project_name)
        pattern = JoinPattern(
            name=name,
            join_keys=join_keys,
            fact_keys=fact_keys,
            source_table=source_table,
            filter=filter_expr,
        )
        config.join_patterns.append(pattern)
        return config

    def add_column_mapping(
        self,
        project_name: str,
        id_source: str,
        physical_column: str,
        data_type: str = "NUMBER",
        description: str = "",
    ) -> MartConfig:
        """Add an ID_SOURCE to physical column mapping.

        Args:
            project_name: Target config.
            id_source: Logical identifier.
            physical_column: Actual column name.
            data_type: SQL data type.
            description: Optional description.

        Returns:
            Updated MartConfig.

        Raises:
            KeyError: If project_name not found.
        """
        config = self._require(project_name)
        mapping = DynamicColumnMapping(
            id_source=id_source,
            physical_column=physical_column,
            data_type=data_type,
            description=description or None,
        )
        config.column_map.append(mapping)
        return config

    def set_flags(
        self,
        project_name: str,
        has_sign_change: Optional[bool] = None,
        has_exclusions: Optional[bool] = None,
        has_group_filter_precedence: Optional[bool] = None,
    ) -> MartConfig:
        """Set boolean pipeline flags on a config.

        Only provided flags are updated; None values are ignored.
        """
        config = self._require(project_name)
        if has_sign_change is not None:
            config.has_sign_change = has_sign_change
        if has_exclusions is not None:
            config.has_exclusions = has_exclusions
        if has_group_filter_precedence is not None:
            config.has_group_filter_precedence = has_group_filter_precedence
        return config

    # ------------------------------------------------------------------
    # Hierarchy integration
    # ------------------------------------------------------------------

    def from_hierarchy_project(
        self,
        project_name: str,
        hierarchy_data: Dict[str, Any],
    ) -> MartConfig:
        """Generate a MartConfig from an existing hierarchy project.

        Reads hierarchy metadata (levels, formulas, mappings) and
        populates a new config with sensible defaults.

        Args:
            project_name: Name for the new mart config.
            hierarchy_data: Dict from hierarchy service containing
                levels, formulas, source_mappings, etc.

        Returns:
            The created MartConfig.
        """
        levels = hierarchy_data.get("levels", [])
        formulas = hierarchy_data.get("formulas", [])
        source_mappings = hierarchy_data.get("source_mappings", [])
        report_type_str = hierarchy_data.get("report_type", "Custom")

        config = self.create_config(
            project_name=project_name,
            report_type=report_type_str,
            hierarchy_table=hierarchy_data.get("hierarchy_table", ""),
            mapping_table=hierarchy_data.get("mapping_table", ""),
            description=f"Auto-generated from hierarchy: {hierarchy_data.get('name', project_name)}",
        )

        # Infer column mappings from source_mappings
        for sm in source_mappings:
            src = sm.get("source_column", "")
            tgt = sm.get("target_column", src)
            if src:
                self.add_column_mapping(project_name, src, tgt)

        # Enable sign change if any formula uses SUBTRACT
        if any(f.get("operation", "").upper() == "SUBTRACT" for f in formulas):
            config.has_sign_change = True

        logger.info(
            "Generated config from hierarchy: %s (%d levels, %d mappings)",
            project_name,
            len(levels),
            len(config.column_map),
        )
        return config

    # ------------------------------------------------------------------
    # Validation
    # ------------------------------------------------------------------

    def validate_config(self, project_name: str) -> MartValidationResult:
        """Check completeness and consistency of a config.

        Returns:
            MartValidationResult with all checks.
        """
        config = self._require(project_name)
        messages: List[ValidationMessage] = []

        def _check(name: str, passed: bool, msg: str, severity: QualityIssueLevel = QualityIssueLevel.ERROR):
            messages.append(ValidationMessage(check=name, passed=passed, message=msg, severity=severity))

        # Required fields
        _check(
            "hierarchy_table_set",
            bool(config.hierarchy_table),
            "hierarchy_table is required" if not config.hierarchy_table else "OK",
        )
        _check(
            "mapping_table_set",
            bool(config.mapping_table),
            "mapping_table is required" if not config.mapping_table else "OK",
        )
        _check(
            "has_join_patterns",
            len(config.join_patterns) > 0,
            "At least one join pattern is needed for DT_3A" if not config.join_patterns else "OK",
            QualityIssueLevel.WARNING,
        )
        _check(
            "has_column_mappings",
            len(config.column_map) > 0,
            "At least one column mapping is needed for DT_2" if not config.column_map else "OK",
            QualityIssueLevel.WARNING,
        )

        # Consistency checks
        if config.join_patterns:
            all_have_keys = all(p.join_keys for p in config.join_patterns)
            _check(
                "join_patterns_have_keys",
                all_have_keys,
                "All join patterns must have at least one join_key" if not all_have_keys else "OK",
            )
            all_have_facts = all(p.fact_keys for p in config.join_patterns)
            _check(
                "join_patterns_have_facts",
                all_have_facts,
                "All join patterns must have at least one fact_key" if not all_have_facts else "OK",
            )

        # Duplicate checks
        id_sources = [m.id_source for m in config.column_map]
        has_dupes = len(id_sources) != len(set(id_sources))
        _check(
            "no_duplicate_id_sources",
            not has_dupes,
            "Duplicate ID_SOURCE values found" if has_dupes else "OK",
        )

        pattern_names = [p.name for p in config.join_patterns]
        has_dupe_patterns = len(pattern_names) != len(set(pattern_names))
        _check(
            "no_duplicate_pattern_names",
            not has_dupe_patterns,
            "Duplicate join pattern names found" if has_dupe_patterns else "OK",
        )

        checked = len(messages)
        passed = sum(1 for m in messages if m.passed)
        valid = all(
            m.passed for m in messages if m.severity == QualityIssueLevel.ERROR
        )

        return MartValidationResult(
            valid=valid,
            messages=messages,
            checked=checked,
            passed=passed,
        )

    # ------------------------------------------------------------------
    # Export
    # ------------------------------------------------------------------

    def export_yaml(self, project_name: str) -> str:
        """Export config as a dbt-compatible YAML string.

        Returns:
            YAML-formatted string.
        """
        config = self._require(project_name)
        lines = [
            f"# dbt mart config: {config.project_name}",
            f"version: 2",
            f"",
            f"models:",
            f"  - name: {config.project_name}",
            f"    description: \"{config.description or ''}\"",
            f"    config:",
            f"      schema: {config.schema_name}",
            f"      materialized: table",
            f"      tags: [{', '.join(config.tags)}]",
            f"    meta:",
            f"      report_type: \"{config.report_type.value}\"",
            f"      account_segment: \"{config.account_segment}\"",
            f"      measure_prefix: \"{config.measure_prefix}\"",
            f"      has_sign_change: {str(config.has_sign_change).lower()}",
            f"      has_exclusions: {str(config.has_exclusions).lower()}",
            f"      has_group_filter_precedence: {str(config.has_group_filter_precedence).lower()}",
        ]

        if config.column_map:
            lines.append("    columns:")
            for m in config.column_map:
                lines.append(f"      - name: {m.physical_column}")
                lines.append(f"        description: \"ID_SOURCE={m.id_source}\"")
                lines.append(f"        data_type: {m.data_type}")

        return "\n".join(lines) + "\n"

    # ------------------------------------------------------------------
    # Internal
    # ------------------------------------------------------------------

    def _require(self, project_name: str) -> MartConfig:
        """Get a config or raise KeyError."""
        config = self._configs.get(project_name)
        if config is None:
            raise KeyError(f"Mart config not found: {project_name}")
        return config
