from __future__ import annotations

from datetime import datetime, timezone
from typing import Any


ORDER_CONTRACT_VERSION = 1
ORDER_MODES = {"CAPTURE_DATE", "PREMIERE_SELECTION_API"}


def _parse_capture_date(value: Any) -> tuple[datetime, bool] | None:
    if not isinstance(value, str):
        return None
    text = value.strip()
    if not text:
        return None
    if text.endswith(("Z", "z")):
        text = text[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(text)
    except ValueError:
        return None
    return parsed, parsed.tzinfo is not None and parsed.utcoffset() is not None


def _date_sort_key(parsed: datetime, aware: bool) -> datetime:
    if aware:
        return parsed.astimezone(timezone.utc)
    return parsed


def plan_session_order(manifest: dict[str, Any]) -> dict[str, Any]:
    if manifest.get("contractVersion") != ORDER_CONTRACT_VERSION:
        raise ValueError("Session manifest contractVersion is unsupported.")
    sources = manifest.get("sources")
    if not isinstance(sources, list) or not sources:
        raise ValueError("Session manifest sources must be a non-empty list.")

    source_ids: list[str] = []
    selection_indices: list[int] = []
    parsed_dates: list[tuple[datetime, bool] | None] = []
    for position, source in enumerate(sources):
        if not isinstance(source, dict):
            raise ValueError(f"Session source {position} must be an object.")
        source_id = source.get("sourceId")
        selection_index = source.get("selectionIndex")
        if not isinstance(source_id, str) or not source_id:
            raise ValueError(f"Session source {position} has no sourceId.")
        if source_id in source_ids:
            raise ValueError("Session source IDs must be unique.")
        if not isinstance(selection_index, int) or selection_index < 0:
            raise ValueError(f"Session source {source_id} has an invalid selectionIndex.")
        source_ids.append(source_id)
        selection_indices.append(selection_index)
        parsed_dates.append(_parse_capture_date(source.get("captureDateRaw")))

    if sorted(selection_indices) != list(range(len(sources))):
        raise ValueError(
            "Session selectionIndex values must be unique, contiguous, and zero-based."
        )
    if selection_indices != list(range(len(sources))):
        raise ValueError("Session sources must be written in Premiere selection-API order.")

    warnings: list[dict[str, Any]] = []
    complete_dates = all(item is not None for item in parsed_dates)
    awareness = {item[1] for item in parsed_dates if item is not None}
    coherent_dates = complete_dates and len(awareness) == 1
    if coherent_dates:
        aware = next(iter(awareness))
        sortable = [
            (
                _date_sort_key(parsed_dates[index][0], aware),  # type: ignore[index]
                selection_indices[index],
                source_ids[index],
                index,
            )
            for index in range(len(sources))
        ]
        sortable.sort()
        ordered_indices = [item[3] for item in sortable]
        order_mode = "CAPTURE_DATE"
        ties: dict[datetime, list[int]] = {}
        for item in sortable:
            ties.setdefault(item[0], []).append(item[3])
        for tied_indices in ties.values():
            if len(tied_indices) > 1:
                warnings.append(
                    {
                        "reasonCode": "CAPTURE_DATE_TIE",
                        "sourceIds": [source_ids[index] for index in tied_indices],
                        "message": (
                            "Two or more clips have the same capture date; Premiere "
                            "selection-API order was used to break the tie."
                        ),
                    }
                )
    else:
        ordered_indices = list(range(len(sources)))
        order_mode = "PREMIERE_SELECTION_API"
        if not complete_dates:
            reason_code = "CAPTURE_DATE_UNAVAILABLE"
            message = (
                "At least one clip has no usable capture date; the complete session "
                "uses Premiere selection-API order."
            )
        else:
            reason_code = "CAPTURE_DATE_TIMEZONE_MIXED"
            message = (
                "Capture dates mix timezone-aware and timezone-free values; the "
                "complete session uses Premiere selection-API order."
            )
        warnings.append(
            {
                "reasonCode": reason_code,
                "sourceIds": source_ids,
                "message": message,
            }
        )

    ordered_sources = []
    for source_order, source_index in enumerate(ordered_indices):
        parsed = parsed_dates[source_index]
        ordered_sources.append(
            {
                "sourceId": source_ids[source_index],
                "sourceOrder": source_order,
                "selectionIndex": selection_indices[source_index],
                "captureDateDisplay": (
                    parsed[0].isoformat() if parsed is not None else ""
                ),
            }
        )
    result = {
        "contractVersion": ORDER_CONTRACT_VERSION,
        "orderMode": order_mode,
        "orderedSourceIds": [item["sourceId"] for item in ordered_sources],
        "sources": ordered_sources,
        "warnings": warnings,
    }
    validate_session_order(result, source_ids)
    return result


def validate_session_order(result: dict[str, Any], issued_source_ids: list[str]) -> None:
    if result.get("contractVersion") != ORDER_CONTRACT_VERSION:
        raise ValueError("Session-order contractVersion is unsupported.")
    if result.get("orderMode") not in ORDER_MODES:
        raise ValueError("Session-order mode is unsupported.")
    ordered_source_ids = result.get("orderedSourceIds")
    if (
        not isinstance(ordered_source_ids, list)
        or len(ordered_source_ids) != len(issued_source_ids)
        or len(set(ordered_source_ids)) != len(ordered_source_ids)
        or set(ordered_source_ids) != set(issued_source_ids)
    ):
        raise ValueError("Session-order source IDs are not the exact issued permutation.")
    sources = result.get("sources")
    if not isinstance(sources, list) or len(sources) != len(issued_source_ids):
        raise ValueError("Session-order sources are inconsistent.")
    for source_order, source in enumerate(sources):
        if (
            not isinstance(source, dict)
            or source.get("sourceId") != ordered_source_ids[source_order]
            or source.get("sourceOrder") != source_order
            or not isinstance(source.get("selectionIndex"), int)
            or source["selectionIndex"] < 0
            or not isinstance(source.get("captureDateDisplay"), str)
        ):
            raise ValueError("Session-order source record is inconsistent.")
    warnings = result.get("warnings")
    if not isinstance(warnings, list):
        raise ValueError("Session-order warnings must be a list.")
    for warning in warnings:
        if (
            not isinstance(warning, dict)
            or not isinstance(warning.get("reasonCode"), str)
            or not warning["reasonCode"]
            or not isinstance(warning.get("message"), str)
            or not warning["message"]
            or not isinstance(warning.get("sourceIds"), list)
            or any(source_id not in issued_source_ids for source_id in warning["sourceIds"])
        ):
            raise ValueError("Session-order warning is inconsistent.")
