#!/usr/bin/env python3
"""Command line front end for the heavy-haul-agent S3 integration.

    python cli.py classify FILE...            # show the folder that would be used
    python cli.py upload FILE... [--yes]      # classify and upload
    python cli.py list                        # every object, grouped by folder
    python cli.py url KEY [--expires 3600]    # presigned read URL (default 1h)
    python cli.py lifecycle                   # print the raw-media expiry rule
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

# Make sibling modules importable no matter where this is run from.
sys.path.insert(0, str(Path(__file__).resolve().parent))

try:  # optional convenience: load S3/.env if python-dotenv is installed
    from dotenv import load_dotenv

    load_dotenv(Path(__file__).resolve().parent / ".env")
except ImportError:
    pass

from classifier import classify_file  # noqa: E402
from s3_client import S3IntegrationError  # noqa: E402
from s3_config import (  # noqa: E402
    RAW_MEDIA_LIFECYCLE_NOTE,
    bucket_name,
    raw_media_lifecycle_rule,
    region_name,
)
from s3_storage import (  # noqa: E402
    PRESIGNED_URL_EXPIRY_SECONDS,
    list_files,
    presigned_url,
    prompt_for_prefix,
    skip_unclear,
    upload_files,
)


def _human_size(size: int) -> str:
    value = float(size)
    for unit in ("B", "KB", "MB", "GB"):
        if value < 1024 or unit == "GB":
            return f"{value:.0f}{unit}" if unit == "B" else f"{value:.1f}{unit}"
        value /= 1024
    return f"{value:.1f}GB"


def cmd_classify(args: argparse.Namespace) -> int:
    for path in args.paths:
        result = classify_file(path)
        flag = "" if result.confident else "  [needs confirmation]"
        print(f"{result.path.name}{flag}")
        print(f"  key          {result.key}")
        print(f"  content-type {result.content_type}")
        print(f"  why          {result.reason}")
        if result.alternatives:
            print(f"  alternatives {', '.join(result.alternatives)}")
    return 0


def cmd_upload(args: argparse.Namespace) -> int:
    # --yes means "never ask": unclear files are reported and skipped rather
    # than guessed into a folder.
    results = upload_files(
        args.paths,
        bucket=args.bucket,
        prefix=args.prefix,
        confirm=skip_unclear if args.yes else prompt_for_prefix,
        dry_run=args.dry_run,
    )

    exit_code = 0
    for result in results:
        name = result.classification.path.name
        if not result.key:  # nothing was placed: unclear and unconfirmed
            print(f"- {name}: {result.note}")
            exit_code = 1
            continue
        marker = "·" if result.skipped else "✓"
        verb = "would upload ->" if result.skipped else "->"
        print(f"{marker} {name} {verb} s3://{result.bucket}/{result.key}")
        print(f"  content-type {result.content_type}")
        if result.note:
            print(f"  note: {result.note}")
    return exit_code


def cmd_list(args: argparse.Namespace) -> int:
    grouped = list_files(bucket=args.bucket)
    if not grouped:
        print(f"s3://{args.bucket or bucket_name()} is empty.")
        return 0

    for prefix, objects in grouped.items():
        total = sum(obj.size for obj in objects)
        print(f"\n{prefix}  ({len(objects)} file(s), {_human_size(total)})")
        for obj in objects:
            stamp = obj.last_modified.strftime("%Y-%m-%d %H:%M")
            print(f"  {stamp}  {_human_size(obj.size):>8}  {obj.filename}")
    print()
    return 0


def cmd_url(args: argparse.Namespace) -> int:
    url = presigned_url(args.key, bucket=args.bucket, expires_in=args.expires)
    print(url)
    print(f"\n(valid for {args.expires} seconds)", file=sys.stderr)
    return 0


def cmd_lifecycle(args: argparse.Namespace) -> int:
    rule = raw_media_lifecycle_rule()
    print(RAW_MEDIA_LIFECYCLE_NOTE)
    print("\nApply with:\n")
    print(
        f"  aws s3api put-bucket-lifecycle-configuration \\\n"
        f"    --bucket {args.bucket or bucket_name()} \\\n"
        f"    --lifecycle-configuration file://raw-media-lifecycle.json\n"
    )
    print("raw-media-lifecycle.json:\n")
    print(json.dumps(rule, indent=2))
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="cli.py",
        description=f"S3 helper for s3://{bucket_name()} ({region_name()}).",
    )
    parser.add_argument("--bucket", help=f"override the bucket (default {bucket_name()})")
    sub = parser.add_subparsers(dest="command", required=True)

    p_classify = sub.add_parser("classify", help="show the folder a file would go to")
    p_classify.add_argument("paths", nargs="+")
    p_classify.set_defaults(func=cmd_classify)

    p_upload = sub.add_parser("upload", help="classify and upload files")
    p_upload.add_argument("paths", nargs="+")
    p_upload.add_argument("--prefix", help="force this folder instead of classifying")
    p_upload.add_argument(
        "--yes",
        action="store_true",
        help="do not prompt; skip files whose folder is unclear",
    )
    p_upload.add_argument("--dry-run", action="store_true", help="show the plan only")
    p_upload.set_defaults(func=cmd_upload)

    p_list = sub.add_parser("list", help="list objects grouped by folder")
    p_list.set_defaults(func=cmd_list)

    p_url = sub.add_parser("url", help="presigned read URL for a key")
    p_url.add_argument("key")
    p_url.add_argument(
        "--expires",
        type=int,
        default=PRESIGNED_URL_EXPIRY_SECONDS,
        help=f"seconds until expiry (default {PRESIGNED_URL_EXPIRY_SECONDS})",
    )
    p_url.set_defaults(func=cmd_url)

    p_lifecycle = sub.add_parser("lifecycle", help="print the raw-media expiry rule")
    p_lifecycle.set_defaults(func=cmd_lifecycle)

    return parser


def main(argv: list[str] | None = None) -> int:
    args = build_parser().parse_args(argv)
    try:
        return args.func(args)
    except S3IntegrationError as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2
    except FileNotFoundError as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2
    except KeyboardInterrupt:
        print("\naborted", file=sys.stderr)
        return 130


if __name__ == "__main__":
    raise SystemExit(main())
