"""
Open Air Reference Manager

Manages open air reference sweep data for background subtraction and normalization.
References are stored per preset configuration and can be captured from live sweeps.
"""

import json
import shutil
from datetime import datetime
from pathlib import Path

from vna_system.core import config as cfg
from vna_system.core.logging.logger import get_component_logger
from vna_system.core.acquisition.sweep_buffer import SweepData
from vna_system.core.settings.preset_manager import ConfigPreset

logger = get_component_logger(__file__)


class ReferenceInfo:
    """Information about a stored reference."""

    def __init__(
        self,
        name: str,
        timestamp: datetime,
        preset_filename: str,
        description: str = "",
        metadata: dict | None = None
    ):
        self.name = name
        self.timestamp = timestamp
        self.preset_filename = preset_filename
        self.description = description
        self.metadata = metadata or {}

    def to_dict(self) -> dict:
        """Convert to dictionary for JSON serialization."""
        return {
            "name": self.name,
            "timestamp": self.timestamp.isoformat(),
            "preset_filename": self.preset_filename,
            "description": self.description,
            "metadata": self.metadata
        }

    @classmethod
    def from_dict(cls, data: dict) -> "ReferenceInfo":
        """Create from dictionary loaded from JSON."""
        return cls(
            name=data["name"],
            timestamp=datetime.fromisoformat(data["timestamp"]),
            preset_filename=data["preset_filename"],
            description=data.get("description", ""),
            metadata=data.get("metadata", {})
        )


class ReferenceManager:
    """
    Manages open air reference sweep data.

    References are stored in the following structure:
    references/
    ├── current_reference.json  # Current active reference metadata
    ├── <preset_name>/
    │   ├── <reference_name>/
    │   │   ├── info.json       # Reference metadata
    │   │   └── sweep_data.json # Raw sweep data
    │   └── ...
    └── ...
    """

    def __init__(self):
        self.references_dir = Path(cfg.BASE_DIR) / "references"
        self.current_reference_file = self.references_dir / "current_reference.json"

        # Ensure directory structure exists
        self.references_dir.mkdir(exist_ok=True)

        logger.info("ReferenceManager initialized", references_dir=str(self.references_dir))

    # ------------------------------------------------------------------ #
    # Reference Creation and Storage
    # ------------------------------------------------------------------ #

    def create_reference(
        self,
        name: str,
        preset: ConfigPreset,
        sweep_data: SweepData,
        description: str = "",
        metadata: dict | None = None
    ) -> ReferenceInfo:
        """
        Create a new reference from sweep data.

        Args:
            name: Unique name for the reference
            preset: Current configuration preset
            sweep_data: Raw sweep data to store
            description: Optional description
            metadata: Optional additional metadata

        Returns:
            ReferenceInfo: Created reference information

        Raises:
            ValueError: If reference name already exists for this preset
            OSError: If file operations fail
        """
        if not name or not name.strip():
            raise ValueError("Reference name cannot be empty")

        preset_dir = self._get_preset_dir(preset)
        reference_dir = preset_dir / name

        if reference_dir.exists():
            raise ValueError(f"Reference '{name}' already exists for preset {preset.filename}")

        # Create reference directory
        reference_dir.mkdir(parents=True, exist_ok=True)

        # Create reference info
        reference_info = ReferenceInfo(
            name=name,
            timestamp=datetime.now(),
            preset_filename=preset.filename,
            description=description.strip(),
            metadata=metadata or {}
        )

        try:
            # Save reference metadata
            info_file = reference_dir / "info.json"
            with info_file.open("w", encoding="utf-8") as f:
                json.dump(reference_info.to_dict(), f, indent=2, ensure_ascii=False)

            # Save sweep data
            sweep_file = reference_dir / "sweep_data.json"
            sweep_dict = {
                "sweep_number": sweep_data.sweep_number,
                "timestamp": sweep_data.timestamp,
                "points": sweep_data.points,
                "total_points": sweep_data.total_points
            }
            with sweep_file.open("w", encoding="utf-8") as f:
                json.dump(sweep_dict, f, indent=2)

            logger.info(
                "Created reference",
                name=name,
                preset=preset.filename,
                sweep_number=sweep_data.sweep_number,
                points=len(sweep_data.points)
            )

            return reference_info

        except Exception as exc:
            # Clean up on failure
            if reference_dir.exists():
                shutil.rmtree(reference_dir, ignore_errors=True)
            logger.error("Failed to create reference", name=name, error=repr(exc))
            raise

    def delete_reference(self, name: str, preset: ConfigPreset) -> bool:
        """
        Delete a reference.

        Args:
            name: Reference name to delete
            preset: Configuration preset

        Returns:
            bool: True if deleted successfully
        """
        preset_dir = self._get_preset_dir(preset)
        reference_dir = preset_dir / name

        if not reference_dir.exists():
            logger.warning("Reference not found for deletion", name=name, preset=preset.filename)
            return False

        try:
            # Check if this is the current reference
            current_ref = self.get_current_reference(preset)
            if current_ref and current_ref.name == name:
                self.clear_current_reference()

            shutil.rmtree(reference_dir)
            logger.info("Deleted reference", name=name, preset=preset.filename)
            return True

        except Exception as exc:
            logger.error("Failed to delete reference", name=name, error=repr(exc))
            return False

    # ------------------------------------------------------------------ #
    # Reference Selection and Retrieval
    # ------------------------------------------------------------------ #

    def set_current_reference(self, name: str, preset: ConfigPreset) -> bool:
        """
        Set the current active reference.

        Args:
            name: Reference name to activate
            preset: Configuration preset

        Returns:
            bool: True if set successfully
        """
        preset_dir = self._get_preset_dir(preset)
        reference_dir = preset_dir / name

        if not reference_dir.exists():
            raise ValueError(f"Reference '{name}' not found for preset {preset.filename}")

        try:
            # Write current reference info to JSON file
            current_info = {
                "reference_name": name,
                "preset_filename": preset.filename,
                "preset_dir": Path(preset.filename).stem,
                "updated_at": datetime.now().isoformat()
            }

            with self.current_reference_file.open("w", encoding="utf-8") as f:
                json.dump(current_info, f, indent=2, ensure_ascii=False)

            logger.info("Set current reference", name=name, preset=preset.filename)
            return True

        except Exception as exc:
            logger.error("Failed to set current reference", name=name, error=repr(exc))
            return False

    def get_current_reference(self, preset: ConfigPreset) -> ReferenceInfo | None:
        """
        Get information about the currently active reference.

        Args:
            preset: Configuration preset to check against

        Returns:
            ReferenceInfo | None: Current reference info, or None if no reference is set
                                    or if the current reference doesn't match the preset
        """
        if not self.current_reference_file.exists():
            return None

        try:
            with self.current_reference_file.open("r", encoding="utf-8") as f:
                current_info = json.load(f)

            reference_name = current_info.get("reference_name")
            preset_filename = current_info.get("preset_filename")

            if not reference_name or not preset_filename:
                logger.warning("Invalid current reference file format")
                return None

            # Check if reference matches current preset
            if preset_filename != preset.filename:
                logger.debug(
                    "Current reference is for different preset",
                    reference_preset=preset_filename,
                    current_preset=preset.filename
                )
                return None

            # Load full reference info
            preset_dir = self._get_preset_dir(preset)
            reference_dir = preset_dir / reference_name
            info_file = reference_dir / "info.json"

            if not info_file.exists():
                logger.warning("Current reference missing info.json", name=reference_name)
                return None

            with info_file.open("r", encoding="utf-8") as f:
                info_data = json.load(f)

            return ReferenceInfo.from_dict(info_data)

        except Exception as exc:
            logger.warning("Failed to get current reference", error=repr(exc))
            return None

    def clear_current_reference(self) -> bool:
        """
        Clear the current reference selection.

        Returns:
            bool: True if cleared successfully
        """
        try:
            if self.current_reference_file.exists():
                self.current_reference_file.unlink()
                logger.info("Cleared current reference")
            return True
        except Exception as exc:
            logger.error("Failed to clear current reference", error=repr(exc))
            return False

    def get_current_reference_sweep(self, preset: ConfigPreset) -> SweepData | None:
        """
        Get the sweep data for the currently active reference.

        Args:
            preset: Configuration preset

        Returns:
            SweepData | None: Current reference sweep data, or None if no reference is active
        """
        current_ref = self.get_current_reference(preset)
        if not current_ref:
            return None

        return self.get_reference_sweep(current_ref.name, preset)

    def get_reference_sweep(self, name: str, preset: ConfigPreset) -> SweepData | None:
        """
        Get sweep data for a specific reference.

        Args:
            name: Reference name
            preset: Configuration preset

        Returns:
            SweepData | None: Reference sweep data, or None if not found
        """
        preset_dir = self._get_preset_dir(preset)
        reference_dir = preset_dir / name
        sweep_file = reference_dir / "sweep_data.json"

        if not sweep_file.exists():
            logger.warning("Reference sweep data not found", name=name, preset=preset.filename)
            return None

        try:
            with sweep_file.open("r", encoding="utf-8") as f:
                sweep_dict = json.load(f)

            return SweepData(
                sweep_number=sweep_dict["sweep_number"],
                timestamp=sweep_dict["timestamp"],
                points=sweep_dict["points"],
                total_points=sweep_dict["total_points"]
            )

        except Exception as exc:
            logger.error("Failed to load reference sweep data", name=name, error=repr(exc))
            return None

    # ------------------------------------------------------------------ #
    # Reference Listing and Information
    # ------------------------------------------------------------------ #

    def list_references(self, preset: ConfigPreset) -> list[ReferenceInfo]:
        """
        List all references for a given preset.

        Args:
            preset: Configuration preset

        Returns:
            list[ReferenceInfo]: List of available references, sorted by timestamp (newest first)
        """
        preset_dir = self._get_preset_dir(preset)

        if not preset_dir.exists():
            return []

        references = []

        for ref_dir in preset_dir.iterdir():
            if not ref_dir.is_dir():
                continue

            info_file = ref_dir / "info.json"
            if not info_file.exists():
                logger.warning("Reference missing info.json", reference_dir=str(ref_dir))
                continue

            try:
                with info_file.open("r", encoding="utf-8") as f:
                    info_data = json.load(f)

                reference_info = ReferenceInfo.from_dict(info_data)
                references.append(reference_info)

            except Exception as exc:
                logger.warning("Failed to load reference info", reference_dir=str(ref_dir), error=repr(exc))

        # Sort by timestamp, newest first
        references.sort(key=lambda r: r.timestamp, reverse=True)
        return references

    def get_reference_info(self, name: str, preset: ConfigPreset) -> ReferenceInfo | None:
        """
        Get information about a specific reference.

        Args:
            name: Reference name
            preset: Configuration preset

        Returns:
            ReferenceInfo | None: Reference information, or None if not found
        """
        preset_dir = self._get_preset_dir(preset)
        reference_dir = preset_dir / name
        info_file = reference_dir / "info.json"

        if not info_file.exists():
            return None

        try:
            with info_file.open("r", encoding="utf-8") as f:
                info_data = json.load(f)

            return ReferenceInfo.from_dict(info_data)

        except Exception as exc:
            logger.warning("Failed to load reference info", name=name, error=repr(exc))
            return None

    # ------------------------------------------------------------------ #
    # Helper Methods
    # ------------------------------------------------------------------ #

    def _get_preset_dir(self, preset: ConfigPreset) -> Path:
        """Get directory path for a preset's references."""
        # Use preset filename without extension as directory name
        preset_name = Path(preset.filename).stem
        return self.references_dir / preset_name

    def cleanup_orphaned_references(self) -> int:
        """
        Clean up reference directories that don't have valid preset files.

        Returns:
            int: Number of orphaned references cleaned up
        """
        cleaned_count = 0

        for preset_dir in self.references_dir.iterdir():
            if not preset_dir.is_dir() or preset_dir.name == "current_reference":
                continue

            # Check if corresponding preset file exists
            preset_filename = f"{preset_dir.name}.bin"
            preset_path = Path(cfg.BASE_DIR) / "binary_input" / "config_inputs" / preset_filename

            if not preset_path.exists():
                try:
                    shutil.rmtree(preset_dir)
                    cleaned_count += 1
                    logger.info("Cleaned up orphaned reference directory", preset=preset_dir.name)
                except Exception as exc:
                    logger.warning("Failed to clean up orphaned references", preset=preset_dir.name, error=repr(exc))

        if cleaned_count > 0:
            logger.info("Reference cleanup completed", cleaned_count=cleaned_count)

        return cleaned_count