#!/usr/bin/env python3
from __future__ import annotations

import argparse
import csv
import json
import sys
from pathlib import Path


def _bootstrap_toolkit(arguments: list[str]) -> None:
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("--srt-toolkit-dir", required=True)
    parser.add_argument("--lexical-alignment-dir", required=True)
    known, _ = parser.parse_known_args(arguments)
    toolkit = Path(known.srt_toolkit_dir).resolve()
    required = ("transcript.py",)
    missing = [name for name in required if not (toolkit / name).is_file()]
    if missing:
        raise RuntimeError(
            "Transcript assembly requires the complete sibling srt-caption-toolkit "
            f"asset; missing: {', '.join(missing)}."
        )
    lexical = Path(known.lexical_alignment_dir).resolve()
    if not (lexical / "lexical_alignment.py").is_file():
        raise RuntimeError(
            "Transcript assembly requires the complete sibling lexical-alignment "
            "asset; missing: lexical_alignment.py."
        )
    sys.path.insert(0, str(lexical))
    sys.path.insert(0, str(toolkit))
    sys.path.insert(0, str(Path(__file__).resolve().parent))


_bootstrap_toolkit(sys.argv[1:])

from assembly import analyze_assembly  # noqa: E402
from contracts import (  # noqa: E402
    PLACEMENT_COLUMNS,
    SEQUENCE_MARKER_COLUMNS,
    SOURCE_MARKER_COLUMNS,
    SUMMARY_COLUMNS,
    expected_placement_rows,
    expected_sequence_marker_rows,
    expected_source_marker_rows,
    expected_summary_rows,
    validate_analysis,
    validate_tsv,
)
from session_input import load_session_table  # noqa: E402


def _write_json(path: Path, value: object) -> None:
    path.write_text(
        json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
        encoding="utf-8",
    )


def _write_tsv(
    path: Path, columns: tuple[str, ...], rows: list[dict[str, str]]
) -> None:
    with path.open("w", encoding="utf-8", newline="") as handle:
        writer = csv.DictWriter(
            handle, fieldnames=columns, dialect="excel-tab", lineterminator="\n"
        )
        writer.writeheader()
        writer.writerows(rows)


def _load_json(path: Path) -> dict:
    value = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise ValueError(f"{path.name} must contain one JSON object.")
    return value


def _load_session(path: Path) -> dict:
    if path.suffix.casefold() in {".tsv", ".tab"}:
        return load_session_table(path)
    return _load_json(path)


def _validate_session_order_metadata(analysis: dict, manifest: dict) -> None:
    expected = {
        "mode": str(manifest.get("orderMode", "MANIFEST_ORDER")),
        "warningCodes": list(manifest.get("orderWarningCodes", [])),
        "displayNames": [
            str(source.get("displayName", source.get("sourceId", "")))
            for source in manifest.get("sources", [])
        ],
    }
    if analysis.get("sessionOrder") != expected:
        raise ValueError("Analysis session order does not match the issued session manifest.")


def _analyze(args: argparse.Namespace) -> int:
    manifest_path = Path(args.session_manifest).resolve()
    target_path = Path(args.target_file).resolve()
    output_dir = Path(args.output_dir).resolve()
    manifest = _load_session(manifest_path)
    manifest["handleBeforeSeconds"] = args.handle_before
    manifest["handleAfterSeconds"] = args.handle_after
    manifest["blockGapSeconds"] = args.block_gap
    analysis = analyze_assembly(
        manifest,
        target_path.read_text(encoding="utf-8"),
        base_dir=manifest_path.parent,
    )
    issued_ids = [source.get("sourceId") for source in manifest.get("sources", [])]
    validate_analysis(analysis, issued_ids)
    _validate_session_order_metadata(analysis, manifest)
    output_dir.mkdir(parents=True, exist_ok=True)
    _write_json(output_dir / "analysis.json", analysis)
    _write_tsv(
        output_dir / "placements.tsv",
        PLACEMENT_COLUMNS,
        expected_placement_rows(analysis),
    )
    _write_tsv(
        output_dir / "sequence-markers.tsv",
        SEQUENCE_MARKER_COLUMNS,
        expected_sequence_marker_rows(analysis),
    )
    _write_tsv(
        output_dir / "source-markers.tsv",
        SOURCE_MARKER_COLUMNS,
        expected_source_marker_rows(analysis),
    )
    _write_tsv(
        output_dir / "summary.tsv", SUMMARY_COLUMNS, expected_summary_rows(analysis)
    )
    print(json.dumps(analysis["summary"], sort_keys=True))
    return 0


def _validate(args: argparse.Namespace) -> int:
    manifest = _load_session(Path(args.session_manifest).resolve())
    analysis = _load_json(Path(args.analysis).resolve())
    issued_ids = [source.get("sourceId") for source in manifest.get("sources", [])]
    validate_analysis(analysis, issued_ids)
    _validate_session_order_metadata(analysis, manifest)
    counts = {
        "placements": validate_tsv(
            Path(args.placements), PLACEMENT_COLUMNS, expected_placement_rows(analysis)
        ),
        "sequenceMarkers": validate_tsv(
            Path(args.sequence_markers),
            SEQUENCE_MARKER_COLUMNS,
            expected_sequence_marker_rows(analysis),
        ),
        "sourceMarkers": validate_tsv(
            Path(args.source_markers),
            SOURCE_MARKER_COLUMNS,
            expected_source_marker_rows(analysis),
        ),
        "summary": validate_tsv(
            Path(args.summary), SUMMARY_COLUMNS, expected_summary_rows(analysis)
        ),
    }
    print(json.dumps({"valid": True, **counts}, sort_keys=True))
    return 0


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Plan and validate deterministic transcript-based assembly."
    )
    parser.add_argument("--srt-toolkit-dir", required=True)
    parser.add_argument("--lexical-alignment-dir", required=True)
    subparsers = parser.add_subparsers(dest="command", required=True)
    analyze = subparsers.add_parser("analyze")
    analyze.add_argument("--session-manifest", required=True)
    analyze.add_argument("--target-file", required=True)
    analyze.add_argument("--output-dir", required=True)
    analyze.add_argument("--handle-before", type=float, default=0.2)
    analyze.add_argument("--handle-after", type=float, default=0.3)
    analyze.add_argument("--block-gap", type=float, default=0.75)
    analyze.set_defaults(handler=_analyze)
    validate = subparsers.add_parser("validate-plan")
    validate.add_argument("--session-manifest", required=True)
    validate.add_argument("--analysis", required=True)
    validate.add_argument("--placements", required=True)
    validate.add_argument("--sequence-markers", required=True)
    validate.add_argument("--source-markers", required=True)
    validate.add_argument("--summary", required=True)
    validate.set_defaults(handler=_validate)
    return parser


def main() -> int:
    args = _parser().parse_args()
    return args.handler(args)


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as error:
        print(f"ERROR: {error}", file=sys.stderr)
        raise SystemExit(2) from error
