# pylint: disable=too-many-positional-arguments,too-many-locals

"""Helper functions for subtitle presence detection."""

import logging
from typing import List, Tuple

import Levenshtein
from langdetect import detect, LangDetectException

from mini_videorag.processors.ocr.text_utils import (
    clean_and_tokenize,
    filter_ocr_tokens,
    fix_suspicious_tokens,
    SUPPORTED_LANGUAGES,
)
from mini_videorag.core.exceptions import ProcessingError

logger = logging.getLogger(__name__)


def generate_ngrams(tokens: List[str], n: int) -> List[str]:
    """
    Generate character-concatenated n-grams from token list.

    Useful for fuzzy string matching or transcript-to-OCR alignment.

    Args:
        tokens: List of word tokens
        n: Number of words per n-gram

    Returns:
        List of n-gram strings
    """
    return ["".join(tokens[i : i + n]) for i in range(len(tokens) - n + 1)]


def check_language_mismatch(transcript_text: str, expected_lang: str) -> dict:
    """
    Detect language mismatch between transcript and expected language.

    Args:
        transcript_text: The transcript text to analyze
        expected_lang: The expected language code (e.g., 'en', 'fr', 'de')

    Returns:
        dict with detected_language, language_mismatch, and explanation
    """
    if not transcript_text or len(transcript_text.strip()) < 10:
        return {
            "detected_language": None,
            "language_mismatch": False,
            "explanation": "Transcript too short for language detection",
        }

    try:
        detected = detect(transcript_text)
        mismatch = detected != expected_lang
        explanation = None

        if mismatch:
            explanation = (
                f"Language mismatch: expected '{expected_lang}' " f"but transcript is '{detected}'"
            )
        else:
            explanation = f"Language match confirmed: '{detected}'"

        return {
            "detected_language": detected,
            "language_mismatch": mismatch,
            "explanation": explanation,
        }
    except (LangDetectException, ProcessingError) as e:
        logger.warning(f"Language detection failed: {str(e)}")
        return {
            "detected_language": None,
            "language_mismatch": False,
            "explanation": f"Language detection error: {str(e)}",
        }


def calculate_subtitle_presence_score(
    transcript_text: str,
    ocr_tokens: List[str],
    lang: str,
    fuzzy: bool = True,
    text_similarity_threshold: float = 0.8,
    min_ocr_frame_coverage: float = 0.85,
    frames_with_ocr: int = 0,
    total_frames: int = 1,
    n_gram_range: Tuple[int, int] = (2, 5),
) -> tuple:
    """
    Calculate subtitle presence score by comparing transcript and OCR tokens.

    Args:
        transcript_text: Clean transcription text
        ocr_tokens: List of filtered OCR tokens
        lang: ISO language code
        fuzzy: Enable fuzzy matching using Levenshtein distance
        text_similarity_threshold: Similarity ratio threshold for fuzzy matching
        min_ocr_frame_coverage: Minimum frame coverage before penalty applies
        frames_with_ocr: Number of frames with OCR text
        total_frames: Total number of frames processed
        n_gram_range: Tuple (min, max) for n-gram generation range (default: (2, 5))
                     Generates n-grams from min to max-1 (e.g., (2, 5) generates 2, 3, 4-grams)

    Returns:
        Tuple of (final_score, token_info_dict)
    """
    # Process transcript
    if isinstance(transcript_text, list):
        transcript_text = " ".join(transcript_text)

    transcript_tokens = clean_and_tokenize(transcript_text)
    transcript_tokens = fix_suspicious_tokens(transcript_tokens)

    if lang in SUPPORTED_LANGUAGES:
        transcript_tokens = filter_ocr_tokens(transcript_tokens, lang)

    if not transcript_tokens:
        logger.warning("No transcript tokens available for comparison")
        return 0.0, {
            "matched_count": 0,
            "total_count": 0,
            "matched_tokens": [],
            "unmatched_tokens": [],
        }

    # Build n-gram set from OCR tokens
    ocr_ngram_set = set(ocr_tokens)
    n_min, n_max = n_gram_range
    for n in range(n_min, n_max):
        ocr_ngram_set.update(generate_ngrams(ocr_tokens, n))

    # Match tokens
    matched_tokens = []
    unmatched_tokens = []

    for token in transcript_tokens:
        match_found = False
        if fuzzy:
            match_found = any(
                Levenshtein.ratio(token, o) >= text_similarity_threshold for o in ocr_ngram_set
            )
        else:
            match_found = token in ocr_ngram_set

        if match_found:
            matched_tokens.append(token)
        else:
            unmatched_tokens.append(token)

    # Calculate score
    base_score = len(matched_tokens) / len(transcript_tokens) if transcript_tokens else 0.0
    ocr_coverage = frames_with_ocr / total_frames if total_frames > 0 else 0.0
    penalty = min(1.0, ocr_coverage / min_ocr_frame_coverage)
    final_score = base_score * penalty

    logger.info(f"OCR Frame Coverage: {ocr_coverage:.1%}")
    if ocr_coverage < min_ocr_frame_coverage:
        logger.info(
            f"Coverage below threshold ({min_ocr_frame_coverage:.0%}) — "
            f"penalty applied: {penalty:.2f}"
        )
    logger.info(f"Matched {len(matched_tokens)} / {len(transcript_tokens)} transcript tokens")
    logger.info(f"Final Score: {final_score:.1%} (Base Score: {base_score:.1%})")

    token_info = {
        "matched_count": len(matched_tokens),
        "total_count": len(transcript_tokens),
        "matched_tokens": matched_tokens[:20],  # Limit for storage
        "unmatched_tokens": unmatched_tokens[:20],  # Limit for storage
        "base_score": base_score,
        "ocr_coverage": ocr_coverage,
        "penalty": penalty,
    }

    return final_score, token_info
