import argparse
import sys
import os
from pathlib import Path

from .approval import ApprovalOptions, prompt_approval
from .chat_session import ChatSession
from .env_loader import EnvLoaderError, load_env
from .llm_client import LLMClient
from .logger import Logger
from .plan import PlanContext
from .provider import ProviderResolutionError, resolve_provider
from .tool_registry import ToolRegistry
from .tools import (
    ToolError,
    apply_patch,
    list_dir,
    read_file_slice,
    run_shell_command,
    update_plan,
)


def _init_logger() -> Logger:
    return Logger()


def cmd_apply_patch(args: argparse.Namespace) -> int:
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    if not prompt_approval("Apply patch?", options=ApprovalOptions(assume_yes=args.yes)):
        log.warn("Patch aborted by user.")
        return 1

    patch_text = sys.stdin.read() if args.file is None else Path(args.file).read_text(encoding="utf-8")
    try:
        changed = apply_patch(patch_text, base_dir=Path.cwd(), logger=log)
    except ToolError as err:
        log.error(str(err))
        return 1

    log.final(f"Applied patch to {len(changed)} file(s).")
    return 0


def cmd_shell_command(args: argparse.Namespace) -> int:
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    if not prompt_approval(
        f"Run shell_command in {args.workdir}?",
        options=ApprovalOptions(assume_yes=args.yes),
    ):
        log.warn("Command aborted by user.")
        return 1

    try:
        result = run_shell_command(args.command, Path(args.workdir), timeout=args.timeout)
    except ToolError as err:
        log.error(str(err))
        return 1
    except Exception as err:  # pragma: no cover - unexpected execution failures
        log.error(f"Command failed: {err}")
        return 1

    log.info(f"Exit code: {result.exit_code}")
    if result.stdout:
        log.info(f"stdout:\n{result.stdout}", to_stdout=False)
    if result.stderr:
        log.warn(f"stderr:\n{result.stderr}")
    log.final(result.stdout.rstrip("\n"))
    return result.exit_code


def cmd_read_file(args: argparse.Namespace) -> int:
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    try:
        content = read_file_slice(Path(args.file), offset=args.offset, limit=args.limit, indent=args.indent)
    except ToolError as err:
        log.error(str(err))
        return 1
    log.final(content)
    return 0


def cmd_list_dir(args: argparse.Namespace) -> int:
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    try:
        listing = list_dir(
            Path(args.dir),
            offset=args.offset,
            limit=args.limit,
            depth=args.depth,
        )
    except ToolError as err:
        log.error(str(err))
        return 1
    log.final(listing)
    return 0


def cmd_update_plan(args: argparse.Namespace) -> int:
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    log.final(update_plan(args.plan))
    return 0


def cmd_exec(args: argparse.Namespace) -> int:
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    if args.timeout < 1 or args.timeout > 1200:
        log.error("timeout must be between 1 and 1200 seconds")
        return 1

    workdir = Path(args.workdir).resolve()
    if not workdir.exists():
        log.error(f"workdir does not exist: {workdir}")
        return 1

    if not prompt_approval(
        f"Run exec in {workdir}?",
        options=ApprovalOptions(assume_yes=args.yes),
    ):
        log.warn("Command aborted by user.")
        return 1

    try:
        result = run_shell_command(args.command, workdir, timeout=args.timeout)
    except ToolError as err:
        log.error(str(err))
        return 1
    except Exception as err:  # pragma: no cover
        log.error(f"Command failed: {err}")
        return 1

    log.info(f"Exit code: {result.exit_code}")
    if result.stdout:
        log.info(f"stdout:\n{result.stdout}", to_stdout=False)
    if result.stderr:
        log.warn(f"stderr:\n{result.stderr}")
    log.final(result.stdout.rstrip("\n"))
    return result.exit_code


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Phase 0 Codex prototype CLI")
    parser.add_argument("--env", help="Path to .env file (optional)")
    parser.add_argument("-m", "--model", help="Model override (optional)")
    parser.add_argument("--max-turns", type=int, help="Max chat turns (default: unlimited)")
    parser.add_argument("--max-steps", type=int, help="Max plan steps to complete before stopping")
    parser.add_argument("--plan-file", help="Path to plan JSON to inject into chat")
    parser.add_argument("--yes", action="store_true", help="Skip approval prompt")
    parser.add_argument("--auto", action="store_true", help="Codex風フルオート: 承認なしでツール/コマンドを実行")
    parser.add_argument("--manual", action="store_true", help="承認必須モード（明示的にY/N確認する）")
    parser.add_argument("--debug", action="store_true", help="Enable debug output for chat")

    subparsers = parser.add_subparsers(dest="command")

    p_apply = subparsers.add_parser("apply_patch", help="Apply patch from stdin or file")
    p_apply.add_argument("--file", help="Patch file path (default: stdin)")
    p_apply.add_argument("--yes", action="store_true", help="Skip approval prompt")
    p_apply.set_defaults(func=cmd_apply_patch)

    p_shell = subparsers.add_parser("shell_command", help="Run a shell command")
    p_shell.add_argument("command", help="Shell command to execute")
    p_shell.add_argument("--workdir", required=True, help="Absolute working directory")
    p_shell.add_argument("--timeout", type=int, default=60, help="Timeout seconds (default 60)")
    p_shell.add_argument("--yes", action="store_true", help="Skip approval prompt")
    p_shell.set_defaults(func=cmd_shell_command)

    p_read = subparsers.add_parser("read_file", help="Read a file slice")
    p_read.add_argument("-f", "--file", required=True, help="Absolute path to file")
    p_read.add_argument("--offset", type=int, default=1, help="1-indexed line offset")
    p_read.add_argument("--limit", type=int, default=2000, help="Max lines to read (cap at 2000)")
    p_read.add_argument("--indent", action="store_true", help="Return raw text without line numbers")
    p_read.set_defaults(func=cmd_read_file)

    p_list = subparsers.add_parser("list_dir", help="List a directory")
    p_list.add_argument("-d", "--dir", required=True, help="Absolute dir path")
    p_list.add_argument("--offset", type=int, default=1, help="1-indexed entry offset")
    p_list.add_argument("--limit", type=int, default=25, help="Max entries to show")
    p_list.add_argument("--depth", type=int, default=2, help="Max depth to traverse (cap 2)")
    p_list.set_defaults(func=cmd_list_dir)

    p_plan = subparsers.add_parser("update_plan", help="Record a plan update")
    p_plan.add_argument("plan", help="Plan text (JSON/string)")
    p_plan.set_defaults(func=cmd_update_plan)

    p_exec = subparsers.add_parser("exec", help="Run a command non-interactively")
    p_exec.add_argument("command", help="Command to execute")
    p_exec.add_argument("--workdir", default=".", help="Working directory (default: cwd)")
    p_exec.add_argument("--timeout", type=int, default=600, help="Timeout seconds (default 600, max 1200)")
    p_exec.add_argument("--yes", action="store_true", help="Skip approval prompt")
    p_exec.set_defaults(func=cmd_exec)

    parser.add_argument("prompt", nargs="?", help="Prompt to start a one-off chat turn")

    return parser


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    command_names = {"apply_patch", "shell_command", "read_file", "list_dir", "update_plan", "exec"}
    prompt_override = None
    argv_list = list(argv) if argv is not None else sys.argv[1:]
    if argv_list and argv_list[0] not in command_names and not argv_list[0].startswith("-"):
        prompt_override = argv_list[0]
        argv_list = argv_list[1:]
    args = parser.parse_args(argv_list)
    if prompt_override and not getattr(args, "prompt", None):
        args.prompt = prompt_override
    if getattr(args, "command", None):
        return args.func(args)

    # Chat mode (no subcommand)
    log = _init_logger()
    try:
        env = load_env(args.env, logger=log).values
        os.environ.update(env)
        if args.model:
            env["MODEL"] = args.model
        provider = resolve_provider(env, logger=log)
    except (EnvLoaderError, ProviderResolutionError) as err:
        log.error(str(err))
        return 1

    client = LLMClient(provider, logger=log)
    registry = ToolRegistry(logger=log)
    plan_ctx = None
    if getattr(args, "plan_file", None):
        try:
            plan_ctx = PlanContext.from_file(Path(args.plan_file))
        except ToolError as err:
            log.error(str(err))
            return 1

    if args.manual:
        assume_yes = False
    else:
        assume_yes = True if (args.auto or args.yes) else True  # デフォルトはフルオートに寄せる

    session = ChatSession(
        client,
        registry=registry,
        logger=log,
        approval=ApprovalOptions(assume_yes=assume_yes),
        plan_context=plan_ctx,
        max_steps=args.max_steps,
    )

    max_turns = args.max_turns
    turn = 0
    if args.prompt:
        output = session.run_turn(args.prompt, debug=args.debug)
        log.final(output)
        return 0

    try:
        while True:
            if max_turns is not None and turn >= max_turns:
                break
            try:
                user_input = input("> ")
            except EOFError:
                break
            if user_input.strip().lower() in {"exit", "quit", "/q", "/quit"}:
                break
            if not user_input.strip():
                continue
            output = session.run_turn(user_input, debug=args.debug)
            log.final(output)
            turn += 1
    except KeyboardInterrupt:
        pass
    return 0
