"""Binary analysis pipeline orchestrator."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Any

from oscura.analyzers.binary.core.file_access import BinaryFile
from oscura.analyzers.binary.core.results import BinaryAnalysisResult

if TYPE_CHECKING:
    from os import PathLike


class BinaryAnalyzer:
    """Complete binary file analysis pipeline.

    Orchestrates multi-stage analysis of binary files including encoding
    detection, pattern mining, structure inference, and semantic analysis.

    Example:
        >>> analyzer = BinaryAnalyzer("unknown_file.bin")
        >>> results = analyzer.analyze(max_samples=100_000)
        >>> print(results)
        >>> results.to_dict()  # Export to dictionary
    """

    def __init__(self, file_path: str | PathLike[str]) -> None:
        """Initialize binary analyzer.

        Args:
            file_path: Path to binary file to analyze.
        """
        self.file_path = Path(file_path)
        self.binary_file = BinaryFile(self.file_path)
        self.results: BinaryAnalysisResult | None = None

    def analyze(
        self,
        max_samples: int = 100_000,
        enable_structure_inference: bool = True,
        enable_semantic_analysis: bool = True,
    ) -> BinaryAnalysisResult:
        """Run complete analysis pipeline.

        Args:
            max_samples: Maximum samples to load for analysis.
            enable_structure_inference: Enable message/field structure detection.
            enable_semantic_analysis: Enable field type classification.

        Returns:
            Complete analysis results.

        Example:
            >>> analyzer = BinaryAnalyzer("data.bin")
            >>> results = analyzer.analyze(max_samples=100_000)
            >>> print(f"Detected dtype: {results.encoding.dtype}")
            >>> print(f"Found {len(results.patterns)} patterns")
        """
        # Import analysis components (lazy import to avoid circular dependencies)
        from oscura.analyzers.binary.detection.encoding import EncodingDetector
        from oscura.analyzers.binary.detection.patterns import PatternMiner

        # Stage 1: Encoding Detection
        encoding_detector = EncodingDetector()
        encoding = encoding_detector.detect(
            self.binary_file, validation=True, max_samples=max_samples
        )

        # Stage 2: Pattern Mining
        pattern_miner = PatternMiner()
        patterns = pattern_miner.find_patterns(
            self.binary_file, min_length=2, max_length=64, min_occurrences=3
        )

        # Stage 3: Structure Inference (if enabled and patterns found)
        structure = None
        if enable_structure_inference and patterns:
            from oscura.analyzers.binary.detection.structure import StructureInferencer

            structure_inferencer = StructureInferencer()
            structure = structure_inferencer.infer(
                self.binary_file, patterns=patterns, encoding=encoding
            )

            # Stage 4: Semantic Analysis (if enabled and structure found)
            if enable_semantic_analysis and structure is not None and structure.has_messages:
                from oscura.analyzers.binary.inference.fields import SemanticAnalyzer

                semantic_analyzer = SemanticAnalyzer()
                structure.fields = semantic_analyzer.analyze_fields(
                    self.binary_file, structure=structure
                )

        # Calculate overall confidence
        confidence = self._calculate_confidence(encoding, patterns, structure)

        # Build result
        self.results = BinaryAnalysisResult(
            encoding=encoding,
            patterns=patterns,
            structure=structure,
            confidence=confidence,
        )

        return self.results

    def _calculate_confidence(self, encoding: Any, patterns: list[Any], structure: Any) -> float:
        """Calculate overall analysis confidence.

        Args:
            encoding: Encoding detection result.
            patterns: Pattern mining results.
            structure: Structure inference result.

        Returns:
            Overall confidence score (0-1).
        """
        scores = [encoding.confidence]

        # Pattern quality score
        if patterns:
            regular_patterns = sum(1 for p in patterns if p.regular)
            pattern_score = min(1.0, regular_patterns / max(1, len(patterns)))
            scores.append(pattern_score)

        # Structure quality score
        if structure and structure.has_messages:
            scores.append(structure.confidence)

        return sum(scores) / len(scores) if scores else 0.0

    def export_results(self, output_path: str | PathLike[str]) -> None:
        """Export analysis results to JSON.

        Args:
            output_path: Path to output JSON file.

        Example:
            >>> analyzer = BinaryAnalyzer("data.bin")
            >>> results = analyzer.analyze()
            >>> analyzer.export_results("analysis_results.json")
        """
        if self.results is None:
            msg = "No results to export. Run analyze() first."
            raise ValueError(msg)

        import json

        output_path = Path(output_path)
        output_path.write_text(json.dumps(self.results.to_dict(), indent=2))

    def __repr__(self) -> str:
        """String representation."""
        return f"BinaryAnalyzer('{self.file_path}')"

    def __str__(self) -> str:
        """Human-readable string."""
        if self.results:
            return str(self.results)
        return f"BinaryAnalyzer('{self.file_path}', not yet analyzed)"
