import argparse
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Any

from .api.build import reconstruct_file_from_events
from .api.load import load_jsonl
from .api.verify import check_time_limit, template_diff, verify


def resolve_document(
    docs: list[str], template_path: Path, override: str | None
) -> str | None:
    """
    Determine which document from the recording to process.

    Parameters
    ----------
    docs : list[str]
        List of document paths found in the recording
    template_path : Path
        Path to the template file
    override : str | None
        Explicit document name or path override

    Returns
    -------
    str | None
        The resolved document path, or None if no documents exist

    Raises
    ------
    ValueError
        If document resolution is ambiguous or the override doesn't match
    """
    if not docs:
        return None

    if override:
        matches = [
            d for d in docs if d.endswith(override) or Path(d).name == override
        ]
        if not matches:
            raise ValueError(
                f"No document in recording matches '{override}'. Available: {docs}"
            )
        if len(matches) > 1:
            raise ValueError(
                f"Ambiguous document override '{override}'. Matches: {matches}"
            )
        return matches[0]

    template_ext = template_path.suffix
    ext_matches = [d for d in docs if Path(d).suffix == template_ext]
    if len(ext_matches) == 1:
        return ext_matches[0]
    if len(ext_matches) > 1:
        raise ValueError(
            f"Multiple documents share extension '{template_ext}': {ext_matches}. "
            "Use --document to choose one."
        )

    if len(docs) == 1:
        return docs[0]

    raise ValueError(
        "Could not determine document to process. Use --document to select one. "
        f"Available documents: {docs}"
    )


def get_recorded_documents(events: tuple[dict[str, Any], ...]) -> list[str]:
    """
    Extract unique document paths from recording events.

    Parameters
    ----------
    events : tuple[dict[str, Any], ...]
        Recording events loaded from JSONL

    Returns
    -------
    list[str]
        Sorted list of unique document paths
    """
    documents = {
        e.get("document")
        for e in events
        if "document" in e and e.get("document") is not None
    }
    return sorted([d for d in documents if d is not None])


def filter_events_by_document(
    events: tuple[dict[str, Any], ...], document: str | None
) -> tuple[dict[str, Any], ...]:
    """
    Filter events to only those for a specific document.

    Parameters
    ----------
    events : tuple[dict[str, Any], ...]
        All recording events
    document : str | None
        Document path to filter by, or None to return all events

    Returns
    -------
    tuple[dict[str, Any], ...]
        Filtered events
    """
    if document:
        return tuple(e for e in events if e.get("document") == document)
    return events


def display_time_info(time_info: dict[str, Any] | None) -> None:
    """
    Display elapsed time and time limit information.

    Parameters
    ----------
    time_info : dict[str, Any] | None
        Time information from check_time_limit, or None if no time data
    """
    if not time_info:
        return

    print(
        f"Elapsed editing time: {time_info['minutes_elapsed']} minutes",
        file=sys.stderr,
    )

    first_ts = datetime.fromisoformat(
        time_info["first_timestamp"].replace("Z", "+00:00")
    )
    last_ts = datetime.fromisoformat(
        time_info["last_timestamp"].replace("Z", "+00:00")
    )
    time_span = (last_ts - first_ts).total_seconds() / 60

    print(f"Time span (first to last edit): {time_span:.2f} minutes", file=sys.stderr)

    if time_info["exceeds_limit"]:
        print("\nTime limit exceeded!", file=sys.stderr)
        print(f"  Limit: {time_info['time_limit_minutes']} minutes", file=sys.stderr)
        print(f"  First edit: {time_info['first_timestamp']}", file=sys.stderr)
        print(f"  Last edit: {time_info['last_timestamp']}", file=sys.stderr)


def display_suspicious_event(event: dict[str, Any], show_details: bool) -> None:
    """
    Display a single suspicious event.

    Parameters
    ----------
    event : dict[str, Any]
        Suspicious event data
    show_details : bool
        Whether to show detailed autocomplete events
    """
    reason = event.get("reason", "unknown")

    # Handle aggregate auto-complete events
    if event.get("event_index") == -1 and "detailed_events" in event:
        event_count = event["event_count"]
        total_chars = event["total_chars"]
        print(
            f"  Aggregate: {event_count} auto-complete/small paste events "
            f"({total_chars} total chars)",
            file=sys.stderr,
        )

        if show_details:
            print("    Detailed events:", file=sys.stderr)
            for detail in event["detailed_events"]:
                detail_idx = detail["event_index"]
                detail_lines = detail["line_count"]
                detail_chars = detail["char_count"]
                detail_frag = detail["newFragment"]
                print(
                    f"      Event #{detail_idx}: {detail_lines} lines, "
                    f"{detail_chars} chars",
                    file=sys.stderr,
                )
                print("        ```", file=sys.stderr)
                for line in detail_frag.split("\n"):
                    print(f"        {line}", file=sys.stderr)
                print("        ```", file=sys.stderr)

    elif "event_indices" in event:
        indices = event.get("event_indices", [event["event_index"]])
        print(
            f"  Events #{indices[0]}-#{indices[-1]} ({reason}): "
            f"{event['line_count']} lines, {event['char_count']} chars",
            file=sys.stderr,
        )

    else:
        new_fragment = event["newFragment"].replace("\n", "\n    ")
        print(
            f"  Event #{event['event_index']} ({reason}): "
            f"{event['line_count']} lines, {event['char_count']} chars - "
            f"newFragment:\n    ```\n    {new_fragment}\n    ```",
            file=sys.stderr,
        )


def display_suspicious_events(
    suspicious_events: list[dict[str, Any]], show_details: bool
) -> None:
    """
    Display all suspicious events or success message.

    Parameters
    ----------
    suspicious_events : list[dict[str, Any]]
        List of suspicious events detected
    show_details : bool
        Whether to show detailed autocomplete events
    """
    if suspicious_events:
        print("\nSuspicious copy-paste events detected:", file=sys.stderr)
        for event in suspicious_events:
            display_suspicious_event(event, show_details)
    else:
        print("Success! No suspicious events detected.", file=sys.stderr)


def write_json_output(
    output_path: Path,
    document: str,
    time_info: dict[str, Any] | None,
    suspicious_events: list[dict[str, Any]],
) -> None:
    """
    Write verification results to JSON file.

    Parameters
    ----------
    output_path : Path
        Path to output JSON file
    document : str
        Document that was processed
    time_info : dict[str, Any] | None
        Time information from verification
    suspicious_events : list[dict[str, Any]]
        List of suspicious events detected

    Raises
    ------
    Exception
        If file writing fails
    """
    results = {
        "document": document,
        "time_info": time_info,
        "suspicious_events": suspicious_events,
    }

    output_path.parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, "w") as f:
        json.dump(results, f, indent=2)
    print(f"Results written to {output_path}", file=sys.stderr)


def create_parser() -> argparse.ArgumentParser:
    """
    Create and configure the argument parser.

    Returns
    -------
    argparse.ArgumentParser
        Configured argument parser
    """
    parser = argparse.ArgumentParser(
        description="Process and verify code recorder JSONL files"
    )
    parser.add_argument(
        "jsonl_file",
        type=Path,
        help="Path to the compressed JSONL file (*.recording.jsonl.gz)",
    )
    parser.add_argument(
        "template_file",
        type=Path,
        help="Path to the initial template file that was recorded",
    )
    parser.add_argument(
        "-t",
        "--time-limit",
        type=int,
        default=None,
        help="Maximum allowed time in minutes between first and last edit. "
        "If exceeded, recording is flagged.",
    )
    parser.add_argument(
        "-d",
        "--document",
        type=str,
        default=None,
        help="Document path or filename to process from the recording. "
        "Defaults to the document whose extension matches the template file.",
    )
    parser.add_argument(
        "-o",
        "--output-json",
        type=Path,
        default=None,
        help="Path to output JSON file with verification results "
        "(time info and suspicious events).",
    )
    parser.add_argument(
        "-s",
        "--show-autocomplete-details",
        action="store_true",
        help="Show individual auto-complete events in addition to "
        "aggregate statistics",
    )
    return parser


def main() -> int:
    """
    Main entry point for the CLI application.

    Returns
    -------
    int
        Exit code (0 for success, 1 for errors)
    """
    parser = create_parser()
    args = parser.parse_args()

    # Load JSONL file
    try:
        json_data = load_jsonl(args.jsonl_file)
    except FileNotFoundError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1
    except (ValueError, IOError) as e:
        print(f"Error loading JSONL file: {e}", file=sys.stderr)
        return 1

    # Resolve which document to process
    recorded_docs = get_recorded_documents(json_data)
    try:
        target_document = resolve_document(
            recorded_docs, args.template_file, args.document
        )
    except ValueError as e:
        print(f"Error determining document: {e}", file=sys.stderr)
        return 1

    # Filter events for target document
    doc_events = filter_events_by_document(json_data, target_document)
    if target_document and not doc_events:
        print(
            f"Error: No events found for document '{target_document}'",
            file=sys.stderr,
        )
        return 1

    print(f"Processing: {target_document or args.template_file}", file=sys.stderr)

    # Read template file
    try:
        template_data = args.template_file.read_text()
    except FileNotFoundError:
        print(
            f"Error: Template file not found: {args.template_file}", file=sys.stderr
        )
        return 1
    except Exception as e:
        print(f"Error reading template file: {e}", file=sys.stderr)
        return 1

    # Check and display time information
    time_info = check_time_limit(doc_events, args.time_limit)
    display_time_info(time_info)

    # Verify and process the recording
    try:
        template_data, suspicious_events = verify(template_data, doc_events)
        reconstructed = reconstruct_file_from_events(
            doc_events, template_data, document_path=target_document
        )
        print(reconstructed)

        # Display suspicious events
        display_suspicious_events(suspicious_events, args.show_autocomplete_details)

        # Write JSON output if requested
        if args.output_json:
            try:
                write_json_output(
                    args.output_json,
                    target_document or str(args.template_file),
                    time_info,
                    suspicious_events,
                )
            except Exception as e:
                print(f"Error writing JSON output: {e}", file=sys.stderr)
                return 1

    except ValueError as e:
        print("File failed verification from template!", file=sys.stderr)
        print(str(e), file=sys.stderr)
        try:
            print(template_diff(template_data, doc_events), file=sys.stderr)
        except Exception:
            pass
        return 1
    except Exception as e:
        print(f"Error processing file: {type(e).__name__}: {e}", file=sys.stderr)
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())
