"""DeduplicationManager — coordinates similarity detection and consolidation."""

from __future__ import annotations

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

from ..protocols.deduplication import DeduplicationConfig
from .detector import SimilarityDetector
from .operations import (
    ConsolidationOperation,
    ConsolidationOpType,
    DeleteOp,
    KeepOp,
    MergeOp,
    UpdateOp,
    apply_consolidation_operations,
)
from .prompts import format_pair_for_logging, generate_similarity_report

if TYPE_CHECKING:
    from ..core.skillbook import Skillbook

logger = logging.getLogger(__name__)


class DeduplicationManager:
    """Manages similarity detection and feeds info to SkillManager.

    Coordinates:
    1. Computing / updating embeddings for skills
    2. Detecting similar skill pairs
    3. Generating similarity reports for the SkillManager prompt
    4. Parsing and applying consolidation operations

    Satisfies :class:`DeduplicationManagerLike` via ``get_similarity_report``.
    """

    def __init__(self, config: DeduplicationConfig | None = None) -> None:
        self.config = config or DeduplicationConfig()
        self.detector = SimilarityDetector(self.config)

    # ------------------------------------------------------------------
    # DeduplicationManagerLike interface
    # ------------------------------------------------------------------

    def get_similarity_report(self, skillbook: "Skillbook") -> Optional[str]:
        """Generate a similarity report for the SkillManager prompt.

        Should be called **before** the SkillManager runs.

        Returns:
            Formatted report, or ``None`` if no similar pairs found or
            deduplication is disabled.
        """
        if not self.config.enabled:
            return None

        self.detector.ensure_embeddings(skillbook)
        similar_pairs = self.detector.detect_similar_pairs(skillbook)

        if len(similar_pairs) < self.config.min_pairs_to_report:
            if similar_pairs:
                logger.debug(
                    "Found %d similar pairs, below threshold of %d",
                    len(similar_pairs),
                    self.config.min_pairs_to_report,
                )
            return None

        logger.info("Found %d similar skill pairs", len(similar_pairs))
        for skill_a, skill_b, similarity in similar_pairs:
            logger.debug(format_pair_for_logging(skill_a, skill_b, similarity))

        return generate_similarity_report(similar_pairs)

    # ------------------------------------------------------------------
    # Consolidation operation parsing / application
    # ------------------------------------------------------------------

    def parse_consolidation_operations(
        self, response_data: Dict[str, Any]
    ) -> List[ConsolidationOperation]:
        """Parse consolidation operations from SkillManager response data."""
        operations: List[ConsolidationOperation] = []
        raw_ops = response_data.get("consolidation_operations", [])

        if not isinstance(raw_ops, list):
            logger.warning("consolidation_operations is not a list")
            return operations

        for raw_op in raw_ops:
            if not isinstance(raw_op, dict):
                continue
            raw_type = raw_op.get("type", "").upper()

            try:
                op_type = ConsolidationOpType(raw_type)
            except ValueError:
                logger.warning("Unknown consolidation operation type: %r", raw_type)
                continue

            try:
                if op_type is ConsolidationOpType.MERGE:
                    operations.append(
                        MergeOp(
                            source_ids=raw_op.get("source_ids", []),
                            merged_content=raw_op.get("merged_content", ""),
                            keep_id=raw_op.get("keep_id", ""),
                            reasoning=raw_op.get("reasoning", ""),
                        )
                    )
                elif op_type is ConsolidationOpType.DELETE:
                    operations.append(
                        DeleteOp(
                            skill_id=raw_op.get("skill_id", ""),
                            reasoning=raw_op.get("reasoning", ""),
                        )
                    )
                elif op_type is ConsolidationOpType.KEEP:
                    operations.append(
                        KeepOp(
                            skill_ids=raw_op.get("skill_ids", []),
                            differentiation=raw_op.get("differentiation", ""),
                            reasoning=raw_op.get("reasoning", ""),
                        )
                    )
                elif op_type is ConsolidationOpType.UPDATE:
                    operations.append(
                        UpdateOp(
                            skill_id=raw_op.get("skill_id", ""),
                            new_content=raw_op.get("new_content", ""),
                            reasoning=raw_op.get("reasoning", ""),
                        )
                    )
            except Exception as e:
                logger.warning(
                    "Failed to parse consolidation operation (%s): %s",
                    type(e).__name__,
                    e,
                )

        logger.info("Parsed %d consolidation operations", len(operations))
        return operations

    def apply_operations(
        self,
        operations: List[ConsolidationOperation],
        skillbook: "Skillbook",
    ) -> None:
        """Apply consolidation operations to the skillbook."""
        if not operations:
            return
        logger.info("Applying %d consolidation operations", len(operations))
        apply_consolidation_operations(operations, skillbook)

    def apply_operations_from_response(
        self,
        response_data: Dict[str, Any],
        skillbook: "Skillbook",
    ) -> List[ConsolidationOperation]:
        """Parse and apply consolidation operations in one step."""
        operations = self.parse_consolidation_operations(response_data)
        self.apply_operations(operations, skillbook)
        return operations
