from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from caption_profile import CaptionProfile
from operations import Finding
from reference_script_alignment import (
    ReferenceScriptAlignment,
    align_reference_script,
    alignment_report,
    alignment_report_markdown,
)
from sequence import MappedWord, sequence_manifest_to_mapped_words


USABLE_SEQUENCE_STATUSES = {"SUCCEEDED", "SUCCEEDED_WITH_WARNINGS"}


@dataclass(frozen=True)
class ReferenceSequenceAlignment:
    status: str
    alignment: ReferenceScriptAlignment | None
    mapped_words: tuple[MappedWord, ...]
    findings: tuple[Finding, ...]
    sequence_report: dict[str, Any]
    chosen_track_index: int | None

    @property
    def can_generate_srt(self) -> bool:
        return self.alignment is not None and self.alignment.can_generate_srt


def align_reference_script_to_sequence_manifest(
    reference_text: str,
    manifest: dict[str, Any],
    *,
    profile: CaptionProfile,
    chosen_track_index: int | None = None,
) -> ReferenceSequenceAlignment:
    """Align approved text to the same mapped words used by sequence SRT export.

    The manifest must describe the active sequence captured by the caller. This
    adapter never associates an arbitrary existing SRT with a sequence and does
    not infer a different sequence from names or transcript content.
    """
    mapped_words, findings, sequence_report = sequence_manifest_to_mapped_words(
        manifest,
        profile=profile,
        chosen_track_index=chosen_track_index,
    )
    ordered_words = tuple(
        sorted(
            mapped_words,
            key=lambda value: (
                value.word.start_ms,
                value.word.end_ms,
                value.item_id,
                value.segment_index,
                value.word_index,
            ),
        )
    )
    sequence_status = str(sequence_report.get("status", "NO_MAPPED_SPEECH"))
    alignment: ReferenceScriptAlignment | None = None
    if sequence_status in USABLE_SEQUENCE_STATUSES:
        alignment = align_reference_script(
            reference_text,
            [mapped.word for mapped in ordered_words],
        )
        status = alignment.readiness.value
    else:
        status = sequence_status
    return ReferenceSequenceAlignment(
        status=status,
        alignment=alignment,
        mapped_words=ordered_words,
        findings=tuple(findings),
        sequence_report=sequence_report,
        chosen_track_index=chosen_track_index,
    )


def _finding_dict(finding: Finding) -> dict[str, Any]:
    return {
        "severity": finding.severity,
        "code": finding.code,
        "message": finding.message,
        "cueIds": list(finding.cue_ids),
    }


def reference_sequence_report(
    result: ReferenceSequenceAlignment,
) -> dict[str, Any]:
    sequence_report = result.sequence_report
    alignment_value = (
        alignment_report(result.alignment) if result.alignment is not None else None
    )
    transcript_languages_by_item = {
        str(item.get("itemId", "")): str(item.get("transcriptLanguage", ""))
        for item in sequence_report.get("itemReports", [])
        if isinstance(item, dict)
    }
    match_sources: list[dict[str, Any]] = []
    if result.alignment is not None:
        for match in result.alignment.matches:
            transcript_token = result.alignment.transcript_tokens[match.transcript_index]
            mapped = result.mapped_words[transcript_token.source_word_index]
            match_sources.append(
                {
                    "referenceIndex": match.reference_index,
                    "referenceText": result.alignment.reference_tokens[
                        match.reference_index
                    ].text,
                    "transcriptIndex": match.transcript_index,
                    "transcriptText": transcript_token.text,
                    "matchKind": match.kind.value,
                    "itemId": mapped.item_id,
                    "itemName": mapped.item_name,
                    "projectItemRef": mapped.project_item_ref,
                    "audioTrackIndex": mapped.track_index,
                    "audioTrackNumber": mapped.track_index + 1,
                    "audioTrackName": mapped.track_name,
                    "sequenceStartMs": transcript_token.start_ms,
                    "sequenceEndMs": transcript_token.end_ms,
                    "sourceWordStartMs": mapped.source_start_ms,
                    "sourceWordEndMs": mapped.source_end_ms,
                    "transcriptLanguage": transcript_languages_by_item.get(
                        mapped.item_id, ""
                    ),
                    "segmentIndex": mapped.segment_index,
                    "wordIndex": mapped.word_index,
                    "speakerId": mapped.speaker_id,
                    "speakerName": mapped.speaker_name,
                }
            )
    return {
        "version": 1,
        "status": result.status,
        "canGenerateSrt": result.can_generate_srt,
        "sequenceAssociation": {
            "basis": "ACTIVE_SEQUENCE_SOURCE_TRANSCRIPTS",
            "sequenceName": str(sequence_report.get("sequenceName", "")),
            "sequenceStartMs": sequence_report.get("sequenceStartMs"),
            "sequenceEndMs": sequence_report.get("sequenceEndMs"),
            "chosenAudioTrackIndex": result.chosen_track_index,
            "chosenAudioTrackNumber": (
                result.chosen_track_index + 1
                if result.chosen_track_index is not None
                else None
            ),
            "note": (
                "The caller captured this active-sequence manifest for the run. "
                "No sequence was inferred from an SRT filename or transcript text."
            ),
        },
        "sequenceSelection": sequence_report,
        "findings": [_finding_dict(finding) for finding in result.findings],
        "alignment": alignment_value,
        "matchedSourceEvidence": match_sources,
    }


def reference_sequence_report_markdown(
    result: ReferenceSequenceAlignment,
) -> str:
    sequence = result.sequence_report
    sequence_name = str(sequence.get("sequenceName", "")) or "Unnamed sequence"
    chosen_track = (
        f"A{result.chosen_track_index + 1} only"
        if result.chosen_track_index is not None
        else "all eligible unmuted audio tracks"
    )
    lines = [
        "# Reference Script To Active Sequence Report",
        "",
        f"- Result: `{result.status}`",
        f"- Active sequence captured for this run: `{sequence_name}`",
        f"- Dialogue scope: {chosen_track}",
        f"- Mapped transcript words: {len(result.mapped_words)}",
        f"- Transcript language status: `{sequence.get('languageStatus', 'NONE')}`",
        "",
        "This run uses the explicitly captured active sequence. It does not guess a sequence from an SRT filename or from similar transcript text.",
        "",
        "## Timeline material used",
        "",
    ]
    status_labels = {
        "MAPPED": "Used",
        "DEDUPLICATED": "Ignored — duplicate spoken material",
        "SKIPPED_MUTED": "Ignored — muted track",
        "SKIPPED_DISABLED": "Ignored — disabled clip",
        "SKIPPED_NO_TRANSCRIPT": "Ignored — no usable transcript",
        "OUT_OF_SCOPE": "Ignored — outside the chosen dialogue track",
        "UNSUPPORTED": "Needs attention",
    }
    for item in sequence.get("itemReports", []):
        track_index = item.get("trackIndex")
        track = item.get("trackName") or (
            f"A{int(track_index) + 1}" if track_index is not None else "Unknown track"
        )
        name = item.get("itemName") or item.get("itemId") or "Unnamed item"
        status = status_labels.get(str(item.get("status")), str(item.get("status")))
        detail = item.get("reason") or f"{item.get('mappedWordCount', 0)} word(s) mapped"
        lines.append(f"- **{status}** — `{track}` / `{name}`: {detail}")
    if result.findings:
        lines.extend(["", "## Sequence mapping notes", ""])
        for finding in result.findings:
            lines.append(f"- **{finding.code.replace('_', ' ').title()}**: {finding.message}")
    if result.alignment is not None:
        nested_report = alignment_report_markdown(
            result.alignment, heading_level=2
        ).rstrip()
        lines.extend(["", nested_report])
    else:
        lines.extend(
            [
                "",
                "## Reference alignment",
                "",
                "The reference script was not aligned because the active-sequence transcript selection was not safe to use.",
            ]
        )
    return "\n".join(lines).rstrip() + "\n"
