from __future__ import annotations

from datetime import datetime, timezone
from typing import Any


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


def plan_source_order(sources: list[dict[str, Any]]) -> tuple[list[int], str, list[dict[str, Any]]]:
    if not sources:
        raise ValueError("Session 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", position)
        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(_capture_date(source.get("captureDateRaw")))
    if selection_indices != list(range(len(sources))):
        raise ValueError(
            "Session sources must be written in contiguous Premiere selection-API order."
        )

    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}
    warnings: list[dict[str, Any]] = []
    if complete_dates and len(awareness) == 1:
        aware = next(iter(awareness))
        keys: list[tuple[datetime, int, int]] = []
        for index, item in enumerate(parsed_dates):
            assert item is not None
            parsed = item[0].astimezone(timezone.utc) if aware else item[0]
            keys.append((parsed, selection_indices[index], index))
        keys.sort()
        order = [item[2] for item in keys]
        by_date: dict[datetime, list[int]] = {}
        for parsed, _, index in keys:
            by_date.setdefault(parsed, []).append(index)
        for tied in by_date.values():
            if len(tied) > 1:
                warnings.append(
                    {
                        "reasonCode": "CAPTURE_DATE_TIE",
                        "sourceIds": [source_ids[index] for index in tied],
                        "message": (
                            "Capture dates are tied; Premiere selection-API order "
                            "breaks the tie."
                        ),
                    }
                )
        return order, "CAPTURE_DATE", warnings

    reason = (
        "CAPTURE_DATE_UNAVAILABLE"
        if not complete_dates
        else "CAPTURE_DATE_TIMEZONE_MIXED"
    )
    warnings.append(
        {
            "reasonCode": reason,
            "sourceIds": source_ids,
            "message": (
                "The complete session uses Premiere selection-API order because "
                "capture dates are unavailable or not safely comparable."
            ),
        }
    )
    return list(range(len(sources))), "PREMIERE_SELECTION_API", warnings
