"""Thin CLI entrypoint for dotpromptz."""

from __future__ import annotations

import argparse
import asyncio
from importlib.metadata import version
from pathlib import Path

from dotpromptz.errors import DotpromptzError, UpgradeError
from dotpromptz.gitops import commit_and_push, submit_prompt, sync_repo
from dotpromptz.logging import configure_logging, get_logger
from dotpromptz.merge import merge_directory
from dotpromptz.service import build_prompt, run_prompt
from dotpromptz.upgrade import run_upgrade

logger = get_logger(__name__)


def _get_version() -> str:
    """Return the installed package version."""
    try:
        return version('dotpromptz-py')
    except Exception:
        return 'unknown'


def _create_parser() -> argparse.ArgumentParser:
    """Build the command-line parser."""
    parser = argparse.ArgumentParser(
        prog='prompt',
        description='Dotpromptz CLI — run/build .prompt files and merge input directories.',
    )
    parser.add_argument('--version', action='version', version=f'%(prog)s {_get_version()}')
    parser.add_argument(
        '--log-level',
        default='INFO',
        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
        help='Set logging level (default: INFO).',
    )
    parser.add_argument(
        '--upgrade',
        '-U',
        action='store_true',
        default=False,
        help='Upgrade dotpromptz-py to the latest version via uv.',
    )

    subparsers = parser.add_subparsers(dest='command', help='Available commands.')

    run_parser = subparsers.add_parser(
        'run',
        help='Render and execute a .prompt file (calls LLM).',
    )
    run_parser.add_argument('prompt_file', metavar='PROMPT_FILE', help='Path to a .prompt file.')
    run_parser.add_argument(
        '--force',
        action='store_true',
        default=False,
        help='Overwrite output files when they already exist.',
    )

    build_parser = subparsers.add_parser(
        'build',
        help='Render a .prompt file without executing (no LLM call).',
    )
    build_parser.add_argument('prompt_file', metavar='PROMPT_FILE', help='Path to a .prompt file.')

    merge_parser = subparsers.add_parser(
        'merge',
        help='Merge files in a directory into one aggregated output file.',
    )
    merge_parser.add_argument('directory', metavar='DIR', help='Directory containing files to merge.')
    merge_parser.add_argument('--pattern', default=None, help='Optional glob pattern (for example "**/*.json").')
    merge_parser.add_argument(
        '--with-filename',
        '-W',
        action='store_true',
        default=False,
        help='Inject FILENAME=<file stem> into each merged record.',
    )
    merge_parser.add_argument(
        '--force',
        action='store_true',
        default=False,
        help='Overwrite output file if it already exists.',
    )

    git_parser = subparsers.add_parser(
        'git',
        help='Run git automation utilities for sync/push/submit.',
    )
    git_subparsers = git_parser.add_subparsers(
        dest='git_command',
        help='Git automation commands.',
        required=True,
    )
    git_subparsers.add_parser(
        'sync',
        help='Sync local branch with upstream via fast-forward only.',
    )
    git_subparsers.add_parser(
        'push',
        help='Auto stage/commit/push all repository changes.',
    )
    submit_parser = git_subparsers.add_parser(
        'submit',
        help='Push latest changes and run submit workflow.',
    )
    submit_parser.add_argument(
        'prompt_file',
        metavar='PROMPT_FILE',
        help='Path to a .prompt file (must be inside repository).',
    )
    submit_parser.add_argument(
        '--no-mail',
        action='store_true',
        default=False,
        help='Disable email notification for this submit run.',
    )

    return parser


def _map_error_to_exit_code(exc: Exception) -> int:
    """Map known exceptions to process exit codes."""
    if isinstance(exc, KeyboardInterrupt):
        return 130
    if isinstance(exc, (DotpromptzError, ValueError)):
        return 2
    return 1


def cli(args: list[str] | None = None) -> None:
    """CLI main entrypoint."""
    parser = _create_parser()
    parsed = parser.parse_args(args)

    log_dir = configure_logging(level=parsed.log_level)

    if parsed.upgrade:
        try:
            raise SystemExit(run_upgrade())
        except UpgradeError as exc:
            logger.error(str(exc))
            raise SystemExit(1) from exc

    if parsed.command is None:
        parser.print_help()
        raise SystemExit(2)

    try:
        if parsed.command == 'run':
            result = asyncio.run(run_prompt(parsed.prompt_file, log_dir=log_dir, force=parsed.force))
            if result.all_failed:
                raise SystemExit(1)
            raise SystemExit(0)

        if parsed.command == 'build':
            build_prompt(parsed.prompt_file, log_dir=log_dir)
            raise SystemExit(0)

        if parsed.command == 'merge':
            output_path = merge_directory(
                Path(parsed.directory),
                pattern=parsed.pattern,
                with_filename=parsed.with_filename,
                force=parsed.force,
            )
            logger.info(f'Merged output saved → {output_path}')
            raise SystemExit(0)

        if parsed.command == 'git':
            if parsed.git_command == 'sync':
                sync_repo()
                raise SystemExit(0)

            if parsed.git_command == 'push':
                commit_and_push()
                raise SystemExit(0)

            if parsed.git_command == 'submit':
                submit_prompt(parsed.prompt_file, send_mail=not parsed.no_mail)
                raise SystemExit(0)
    except Exception as exc:
        exit_code = _map_error_to_exit_code(exc)
        if exit_code == 130:
            logger.warning('Interrupted by user.')
        elif exit_code == 2:
            logger.error(str(exc))
        else:
            logger.exception('Unexpected CLI failure.', error=str(exc))
        raise SystemExit(exit_code) from exc
