"""
Import tools for reference and usage-reference datasets with conflict resolution.
"""

from dataclasses import dataclass
from pathlib import Path
from typing import (
    Any,
    Dict,
    List,
    Optional,
)

import polars as pl

from .import_session import ImportSessionManager


@dataclass
class ImportConflict:
    """Represents a conflict between existing and new records."""

    conflict_id: str
    key_columns: Dict[str, Any]  # The combination that conflicts
    existing_record: Dict[str, Any]
    new_record: Dict[str, Any]
    conflict_type: str  # 'generic_vs_specific', 'different_values', 'duplicate'

    def get_summary(self) -> str:
        """Get human-readable conflict summary."""
        key_str = ", ".join([f"{k}={v}" for k, v in self.key_columns.items()])
        return f"[{self.conflict_type}] {key_str}"


class ImportAnalyzer:
    """Analyzes imports for conflicts and impact."""

    # Reference dataset key columns
    REFERENCE_KEY_COLS = [
        "cloud_provider",
        "service",
        "usage_family",
        "resource_type",
        "operation",
        "usage_type",
    ]

    # Usage-reference dataset key columns
    USAGE_REFERENCE_KEY_COLS = [
        "cloud_provider",
        "service",
        "usage_family",
        "resource_type",
        "operation",
        "usage_type",
        "pricing_unit",
    ]

    def __init__(self, target_dataset: str):
        self.target_dataset = target_dataset
        self.key_cols = (
            self.REFERENCE_KEY_COLS
            if target_dataset == "reference"
            else self.USAGE_REFERENCE_KEY_COLS
        )

    def analyze_import(
        self, existing_df: pl.DataFrame, new_df: pl.DataFrame
    ) -> Dict[str, Any]:
        """Analyze import for conflicts and impact."""

        # Normalize column names in new data
        new_df = self._normalize_columns(new_df)

        # Find exact duplicates
        exact_duplicates = self._find_exact_duplicates(existing_df, new_df)

        # Find conflicts (same key, different values)
        conflicts = self._find_conflicts(existing_df, new_df)

        # Find net new records
        net_new = self._find_net_new_records(
            existing_df, new_df, conflicts, exact_duplicates
        )

        return {
            "total_new_records": len(new_df),
            "exact_duplicates": len(exact_duplicates),
            "conflicts": len(conflicts),
            "net_new_records": len(net_new),
            "conflicts_list": conflicts,
            "net_new_list": net_new.to_dicts() if len(net_new) > 0 else [],
            "duplicate_list": exact_duplicates.to_dicts()
            if len(exact_duplicates) > 0
            else [],
        }

    def _normalize_columns(self, df: pl.DataFrame) -> pl.DataFrame:
        """Normalize column names using existing patterns."""
        if self.target_dataset == "reference":
            # Use reference column synonyms
            from ...cli.reference import COLUMN_SYNONYMS

            reverse_map = {
                bad: good
                for good, aliases in COLUMN_SYNONYMS.items()
                for bad in aliases
            }
        else:
            # Use usage-reference column synonyms
            from ...cli.usage_reference import COLUMN_SYNONYMS

            reverse_map = {
                bad: good
                for good, aliases in COLUMN_SYNONYMS.items()
                for bad in aliases
            }

        rename_map = {col: reverse_map[col] for col in df.columns if col in reverse_map}
        return df.rename(rename_map)

    def _find_exact_duplicates(
        self, existing_df: pl.DataFrame, new_df: pl.DataFrame
    ) -> pl.DataFrame:
        """Find records that already exist exactly."""
        # Get common columns
        common_cols = [
            col
            for col in self.key_cols
            if col in existing_df.columns and col in new_df.columns
        ]

        if not common_cols:
            return pl.DataFrame()

        # Inner join to find exact matches
        duplicates = new_df.join(
            existing_df.select(common_cols), on=common_cols, how="inner"
        )

        return duplicates

    def _find_conflicts(
        self, existing_df: pl.DataFrame, new_df: pl.DataFrame
    ) -> List[ImportConflict]:
        """Find conflicts between existing and new records."""
        conflicts = []

        # Get available key columns
        available_key_cols = [
            col
            for col in self.key_cols
            if col in existing_df.columns and col in new_df.columns
        ]

        if not available_key_cols:
            return conflicts

        # Join on key columns to find matches
        joined = new_df.join(
            existing_df, on=available_key_cols, how="inner", suffix="_existing"
        )

        if len(joined) == 0:
            return conflicts

        # Check for conflicts in non-key columns
        for row in joined.iter_rows(named=True):
            conflict_found = False
            key_values = {col: row[col] for col in available_key_cols}

            # Compare non-key columns
            new_record = {}
            existing_record = {}

            for col in new_df.columns:
                if col not in available_key_cols:
                    new_value = row.get(col)
                    existing_value = row.get(f"{col}_existing")

                    new_record[col] = new_value
                    existing_record[col] = existing_value

                    if (
                        new_value != existing_value
                        and new_value is not None
                        and existing_value is not None
                    ):
                        conflict_found = True

            if conflict_found:
                # Determine conflict type
                conflict_type = self._classify_conflict(existing_record, new_record)

                conflict_id = f"conflict_{len(conflicts) + 1}"
                conflicts.append(
                    ImportConflict(
                        conflict_id=conflict_id,
                        key_columns=key_values,
                        existing_record=existing_record,
                        new_record=new_record,
                        conflict_type=conflict_type,
                    )
                )

        return conflicts

    def _classify_conflict(self, existing: dict, new: dict) -> str:
        """Classify the type of conflict."""
        # Check for generic vs specific pattern
        existing_resource = existing.get("resource_type", "")
        new_resource = new.get("resource_type", "")

        if existing_resource == "service-usage" and new_resource != "service-usage":
            return "generic_vs_specific"
        elif new_resource == "service-usage" and existing_resource != "service-usage":
            return "specific_vs_generic"
        else:
            return "different_values"

    def _find_net_new_records(
        self,
        existing_df: pl.DataFrame,
        new_df: pl.DataFrame,
        conflicts: List[ImportConflict],
        duplicates: pl.DataFrame,
    ) -> pl.DataFrame:
        """Find records that are genuinely new."""
        available_key_cols = [
            col
            for col in self.key_cols
            if col in existing_df.columns and col in new_df.columns
        ]

        if not available_key_cols:
            return new_df

        # Get conflict and duplicate keys
        conflict_keys = [conflict.key_columns for conflict in conflicts]
        duplicate_keys = (
            duplicates.select(available_key_cols).to_dicts()
            if len(duplicates) > 0
            else []
        )

        # Find records that don't match any existing keys
        net_new_records = []
        for row in new_df.iter_rows(named=True):
            row_key = {col: row[col] for col in available_key_cols}

            if row_key not in conflict_keys and row_key not in duplicate_keys:
                net_new_records.append(row)

        if net_new_records:
            return pl.DataFrame(net_new_records)
        else:
            return pl.DataFrame()


class InteractiveImporter:
    """Handles interactive conflict resolution."""

    def __init__(self, session_manager: ImportSessionManager):
        self.session_manager = session_manager

    def run_interactive_import(
        self,
        source_file: Path,
        target_dataset: str,
        existing_data_path: Path,
        session_id: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Run interactive import with conflict resolution."""

        # Load data
        print(f"📥 Loading source file: {source_file}")
        new_df = pl.read_csv(source_file)

        if target_dataset == "reference":
            existing_df = (
                pl.read_csv(existing_data_path)
                if existing_data_path.exists()
                else pl.DataFrame()
            )
        else:
            existing_df = (
                pl.read_parquet(existing_data_path)
                if existing_data_path.exists()
                else pl.DataFrame()
            )

        # Analyze import
        analyzer = ImportAnalyzer(target_dataset)
        analysis = analyzer.analyze_import(existing_df, new_df)

        print("\n📊 Import Analysis:")
        print(f"   Total new records: {analysis['total_new_records']}")
        print(f"   Exact duplicates: {analysis['exact_duplicates']}")
        print(f"   Conflicts: {analysis['conflicts']}")
        print(f"   Net new records: {analysis['net_new_records']}")

        # Handle case with no conflicts
        if analysis["conflicts"] == 0:
            print("\n✅ No conflicts found! Adding net new records...")
            result_df = self._apply_no_conflict_import(
                existing_df, analysis["net_new_list"]
            )
            self._save_result(result_df, existing_data_path, target_dataset)
            return {
                "status": "completed",
                "conflicts_resolved": 0,
                "records_added": analysis["net_new_records"],
            }

        # Load or create session
        if session_id:
            session = self.session_manager.load_session(session_id)
            if not session:
                print(f"❌ Session {session_id} not found")
                return {"status": "error", "message": "Session not found"}
            print(f"\n🔄 Resuming session: {session.session_id}")
            print(
                f"   Progress: {session.resolved_conflicts}/{session.total_conflicts}"
            )
        else:
            from .import_session import generate_session_id

            session_id = generate_session_id(str(source_file), target_dataset)
            session = self.session_manager.create_session(
                session_id, str(source_file), target_dataset, analysis["conflicts"]
            )
            print(f"\n📝 Created new session: {session_id}")

        # Resolve conflicts interactively
        conflicts = analysis["conflicts_list"]
        starting_index = session.resolved_conflicts

        print("\n🔧 Starting conflict resolution...")
        print(f"   Conflicts to resolve: {len(conflicts) - starting_index}")
        print("   Commands: [R]eplace, [K]eep both, [S]kip, [Q]uit and save")

        for i in range(starting_index, len(conflicts)):
            conflict = conflicts[i]

            print(f"\n--- Conflict {i + 1}/{len(conflicts)} ---")
            print(f"Type: {conflict.conflict_type}")
            print(f"Key: {conflict.get_summary()}")
            print("\nExisting record:")
            self._print_record(conflict.existing_record)
            print("\nNew record:")
            self._print_record(conflict.new_record)

            decision = self._get_user_decision()

            if decision == "quit":
                self.session_manager.save_session(session)
                print(
                    f"\n💾 Session saved. Resume with: --session={session.session_id}"
                )
                return {"status": "paused", "session_id": session.session_id}

            session.add_decision(
                conflict.conflict_id,
                conflict.existing_record,
                conflict.new_record,
                decision,
            )
            self.session_manager.save_session(session)

        # Apply all decisions
        print("\n✅ All conflicts resolved! Applying changes...")
        session.mark_completed()
        self.session_manager.save_session(session)

        result_df = self._apply_decisions(existing_df, analysis, session.decisions)
        self._save_result(result_df, existing_data_path, target_dataset)

        return {
            "status": "completed",
            "conflicts_resolved": len(session.decisions),
            "records_added": analysis["net_new_records"],
        }

    def _print_record(self, record: dict):
        """Print a record in a readable format."""
        for key, value in record.items():
            if value is not None and value != "":
                print(f"  {key}: {value}")

    def _get_user_decision(self) -> str:
        """Get user decision for conflict resolution."""
        while True:
            choice = input("\nDecision [R/K/S/Q]: ").strip().lower()
            if choice in ["r", "replace"]:
                return "replace"
            elif choice in ["k", "keep", "keep_both"]:
                return "keep_both"
            elif choice in ["s", "skip"]:
                return "skip"
            elif choice in ["q", "quit"]:
                return "quit"
            else:
                print(
                    "Invalid choice. Use R (replace), K (keep both), S (skip), or Q (quit)"
                )

    def _apply_no_conflict_import(
        self, existing_df: pl.DataFrame, net_new_records: List[dict]
    ) -> pl.DataFrame:
        """Apply import when there are no conflicts."""
        if not net_new_records:
            return existing_df

        new_records_df = pl.DataFrame(net_new_records)
        return pl.concat([existing_df, new_records_df], how="vertical")

    def _apply_decisions(
        self, existing_df: pl.DataFrame, analysis: dict, decisions: list
    ) -> pl.DataFrame:
        """Apply conflict resolution decisions."""
        # Start with existing data
        result_df = existing_df.clone()

        # Apply each decision
        for decision in decisions:
            if decision.decision == "replace":
                # Remove existing record and add new one
                # This is simplified - would need proper key-based removal
                pass
            elif decision.decision == "keep_both":
                # Add new record alongside existing
                pass
            # 'skip' means do nothing

        # Add net new records
        if analysis["net_new_list"]:
            new_records_df = pl.DataFrame(analysis["net_new_list"])
            result_df = pl.concat([result_df, new_records_df], how="vertical")

        return result_df

    def _save_result(self, df: pl.DataFrame, output_path: Path, target_dataset: str):
        """Save the result to appropriate format."""
        if target_dataset == "reference":
            df.write_csv(output_path)
        else:
            df.write_parquet(output_path)

        print(f"💾 Saved updated {target_dataset} data to: {output_path}")
