from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Optional, Tuple

import numpy as np


class FailureReason(str, Enum):
    no_face = "no_face"
    small_face = "small_face"
    align_fail = "align_fail"
    infer_fail = "infer_fail"
    read_fail = "read_fail"
    det_fail = "det_fail"


@dataclass(frozen=True)
class Detection:
    bbox_xyxy: np.ndarray  # float32 shape (4,) in original image coords
    landmarks5: np.ndarray  # float32 shape (5,2) in original image coords
    score: float


@dataclass(frozen=True)
class ImageResult:
    ok: bool
    reason: Optional[FailureReason]
    detail: str
    emb: Optional[np.ndarray] = None  # float32 (D,) L2-normalized
    det: Optional[Detection] = None


@dataclass
class BuildReport:
    total_person_dirs: int = 0
    enrolled_persons: int = 0
    total_images: int = 0
    processed_images: int = 0
    ok_images: int = 0
    failed_images: int = 0
    per_person_used: Dict[str, int] = None
    per_person_failed: Dict[str, List[Tuple[str, FailureReason, str]]] = None
    failure_reasons: Dict[str, int] = None
    skipped_persons: List[str] = None

    def __post_init__(self) -> None:
        if self.per_person_used is None:
            self.per_person_used = {}
        if self.per_person_failed is None:
            self.per_person_failed = {}
        if self.failure_reasons is None:
            self.failure_reasons = {}
        if self.skipped_persons is None:
            self.skipped_persons = []

    def add_failure(self, person: str, img_path: str, reason: FailureReason, detail: str) -> None:
        self.processed_images += 1
        self.failed_images += 1
        self.failure_reasons[str(reason)] = self.failure_reasons.get(str(reason), 0) + 1
        self.per_person_failed.setdefault(person, []).append((img_path, reason, detail))

    def add_success(self, person: str) -> None:
        self.processed_images += 1
        self.ok_images += 1
        self.per_person_used[person] = self.per_person_used.get(person, 0) + 1
