"""Failure Recovery Coordinator (Component F11)."""

import logging
from abc import ABC, abstractmethod
from typing import List

import numpy as np

from gps_denied.core.chunk_manager import IRouteChunkManager
from gps_denied.core.gpr import IGlobalPlaceRecognition
from gps_denied.core.metric import IMetricRefinement
from gps_denied.schemas.chunk import ChunkStatus

logger = logging.getLogger(__name__)


class IFailureRecoveryCoordinator(ABC):
    @abstractmethod
    def handle_tracking_lost(self, flight_id: str, current_frame_id: int) -> bool:
        pass

    @abstractmethod
    def process_chunk_recovery(self, flight_id: str, chunk_id: str, images: List[np.ndarray]) -> bool:
        pass


class FailureRecoveryCoordinator(IFailureRecoveryCoordinator):
    """Coordinates fallbacks and recovery patterns when visual tracking drops."""

    def __init__(
        self,
        chunk_manager: IRouteChunkManager,
        gpr: IGlobalPlaceRecognition,
        metric_refinement: IMetricRefinement
    ):
        self.chunk_manager = chunk_manager
        self.gpr = gpr
        self.metric_refinement = metric_refinement

    def handle_tracking_lost(self, flight_id: str, current_frame_id: int) -> bool:
        """Called when F07 fails to find sequential matches."""
        logger.warning(f"Tracking lost for flight {flight_id} at frame {current_frame_id}")

        # Create a new active chunk to record new relative frames independently
        self.chunk_manager.create_new_chunk(flight_id, current_frame_id)
        return True

    def process_chunk_recovery(self, flight_id: str, chunk_id: str, images: List[np.ndarray]) -> bool:
        """
        Attempts to find global place recognition matches for a chunk
        and run metric refinement to provide an absolute Sim3 alignment.
        """
        self.chunk_manager.update_chunk_status(flight_id, chunk_id, ChunkStatus.MATCHING)

        candidates = self.gpr.retrieve_candidate_tiles_for_chunk(images, top_k=5)

        if not candidates:
            self.chunk_manager.update_chunk_status(flight_id, chunk_id, ChunkStatus.UNANCHORED)
            return False

        # Take topmost candidate
        best_candidate = candidates[0]

        # We need mock tile for Metric Refinement
        mock_tile = np.zeros((256, 256, 3))

        align_result = self.metric_refinement.align_chunk_to_satellite(
            chunk_images=images,
            satellite_tile=mock_tile,
            tile_bounds=best_candidate.bounds
        )

        if align_result and align_result.matched:
            # Anchor successfully placed on chunk
            active_chunk = self.chunk_manager.get_active_chunk(flight_id)
            if active_chunk and active_chunk.chunk_id == chunk_id:
                active_chunk.has_anchor = True
                active_chunk.anchor_gps = align_result.chunk_center_gps
                self.chunk_manager.update_chunk_status(flight_id, chunk_id, ChunkStatus.ANCHORED)
                return True

        self.chunk_manager.update_chunk_status(flight_id, chunk_id, ChunkStatus.UNANCHORED)
        return False
