import os
import sys
from typing import Any, Dict, Optional

from .pipeline import ObfuscationPipeline, SUPPORTED_ALGORITHMS, _ALGORITHM_REGISTRY

# ── 终端颜色（Windows 兼容） ──────────────────────────────────────────────────
def _supports_color() -> bool:
    return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()

_USE_COLOR = _supports_color()

def _c(text: str, code: str) -> str:
    return f"\033[{code}m{text}\033[0m" if _USE_COLOR else text

def green(text: str) -> str:  return _c(text, "32")
def yellow(text: str) -> str: return _c(text, "33")
def cyan(text: str) -> str:   return _c(text, "36")
def red(text: str) -> str:    return _c(text, "31")
def bold(text: str) -> str:   return _c(text, "1")


# ── 输入辅助函数 ──────────────────────────────────────────────────────────────
def _prompt(message: str, default: Optional[str] = None) -> str:
    """带默认值提示的 input。"""
    hint = f" [{default}]" if default is not None else ""
    try:
        value = input(f"{message}{hint}: ").strip()
    except (EOFError, KeyboardInterrupt):
        print()
        raise
    return value if value else (default or "")


def _prompt_int(message: str, default: int, min_val: int = 1, max_val: int = 9999) -> int:
    """提示输入整数，带范围校验和默认值。"""
    while True:
        raw = _prompt(message, str(default))
        try:
            value = int(raw)
            if min_val <= value <= max_val:
                return value
            print(red(f"  请输入 {min_val}~{max_val} 之间的整数"))
        except ValueError:
            print(red("  请输入有效整数"))


def _prompt_optional_int(message: str) -> Optional[int]:
    """提示输入可选整数（直接回车表示不设置）。"""
    raw = _prompt(message + "（回车跳过）", "")
    if not raw:
        return None
    try:
        return int(raw)
    except ValueError:
        print(yellow("  输入无效，已跳过"))
        return None


def _prompt_optional_str(message: str) -> Optional[str]:
    """提示输入可选字符串（直接回车表示不设置）。"""
    raw = _prompt(message + "（回车跳过）", "")
    return raw if raw else None


def _prompt_file_path(message: str, must_exist: bool = True) -> str:
    """提示输入文件路径，可选校验是否存在。"""
    while True:
        path = _prompt(message).strip('"').strip("'")
        if not path:
            print(red("  路径不能为空"))
            continue
        if must_exist and not os.path.exists(path):
            print(red(f"  文件不存在：{path}"))
            continue
        return path


def _prompt_choice(message: str, choices: list, labels: Optional[list] = None) -> str:
    """展示选项列表，返回用户选择的值。"""
    print(f"\n{message}")
    for index, choice in enumerate(choices, 1):
        label = labels[index - 1] if labels else choice
        print(f"  {cyan(str(index))}. {label}")
    while True:
        raw = _prompt("请输入序号").strip()
        try:
            index = int(raw) - 1
            if 0 <= index < len(choices):
                return choices[index]
            print(red(f"  请输入 1~{len(choices)} 之间的序号"))
        except ValueError:
            print(red("  请输入有效序号"))

# ── 算法参数收集 ──────────────────────────────────────────────────────────────
def _collect_algorithm_params(algorithm: str) -> Dict[str, Any]:
    """根据算法类型交互式收集参数。"""
    entry = _ALGORITHM_REGISTRY[algorithm]
    defaults = entry["defaults"]
    params: Dict[str, Any] = {}

    print(cyan(f"\n  配置 {entry['label']} 参数："))

    if "seed" in defaults:
        params["seed"] = _prompt_optional_int("  随机种子 seed")

    if "iterations" in defaults:
        params["iterations"] = _prompt_int(
            "  迭代次数 iterations", default=defaults["iterations"]
        )

    if "block_size" in defaults:
        params["block_size"] = _prompt_int(
            "  块大小 block_size", default=defaults["block_size"], min_val=4, max_val=256
        )

    if "password" in defaults:
        params["password"] = _prompt_optional_str("  加密密码 password")

    # use_timestamp 对 pipeline 始终关闭，确保可逆性
    if "use_timestamp" in defaults:
        params["use_timestamp"] = False

    return params


# ── 主菜单流程 ────────────────────────────────────────────────────────────────
def _print_banner() -> None:
    print()
    print(bold("╔══════════════════════════════════════════╗"))
    print(bold("║      ImageStego 多层混淆 CLI 工具        ║"))
    print(bold("╚══════════════════════════════════════════╝"))
    print()


def _encrypt_flow() -> None:
    """加密流程：选择图片 -> 逐层添加算法 -> 输出。"""
    print(bold("\n── 加密模式 ──────────────────────────────"))

    input_path = _prompt_file_path("输入图片路径（支持 PNG/JPG）", must_exist=True)
    output_path = _prompt("输出图片路径", default=_default_output(input_path, "_encrypted"))

    pipeline = ObfuscationPipeline()

    print(f"\n{yellow('开始添加混淆层（输入 q 完成添加）')}")

    algorithm_labels = [
        f"{name}  —  {_ALGORITHM_REGISTRY[name]['label']}"
        for name in SUPPORTED_ALGORITHMS
    ]

    while True:
        print(f"\n{pipeline.summary()}")
        print()

        action = _prompt_choice(
            "选择操作：",
            choices=["add", "done", "clear"],
            labels=[
                "添加一层混淆",
                f"完成添加，开始加密（当前 {pipeline.layer_count} 层）",
                "清空所有层重新开始",
            ],
        )

        if action == "clear":
            pipeline.clear_layers()
            print(green("  已清空所有层"))
            continue

        if action == "done":
            if pipeline.layer_count == 0:
                print(red("  至少需要添加一层混淆！"))
                continue
            break

        # action == "add"
        algorithm = _prompt_choice(
            "选择混淆算法：",
            choices=SUPPORTED_ALGORITHMS,
            labels=algorithm_labels,
        )
        params = _collect_algorithm_params(algorithm)
        pipeline.add_layer(algorithm, **params)
        print(green(f"\n  ✓ 已添加第 {pipeline.layer_count} 层：{_ALGORITHM_REGISTRY[algorithm]['label']}"))

    # 执行加密
    print()
    try:
        sidecar = pipeline.encrypt(input_path, output_path)
        print(green(f"\n✓ 加密成功！"))
        print(f"  加密图片：{bold(output_path)}")
        print(f"  层信息文件（解密时需要）：{bold(sidecar)}")
    except Exception as error:
        print(red(f"\n✗ 加密失败：{error}"))

def _decrypt_flow() -> None:
    """解密流程：选择加密图片 -> 自动读取 sidecar -> 逆序解密。"""
    print(bold("\n── 解密模式 ──────────────────────────────"))

    encrypted_path = _prompt_file_path("加密图片路径", must_exist=True)
    output_path = _prompt("还原图片输出路径", default=_default_output(encrypted_path, "_restored"))

    # 检查 sidecar 是否存在
    from .pipeline import _sidecar_path
    default_sidecar = _sidecar_path(encrypted_path)
    if os.path.exists(default_sidecar):
        print(green(f"  ✓ 找到层信息文件：{default_sidecar}"))
        sidecar = default_sidecar
    else:
        print(yellow(f"  未找到默认层信息文件：{default_sidecar}"))
        sidecar = _prompt_file_path("请手动指定 .layers.json 文件路径", must_exist=True)

    pipeline = ObfuscationPipeline()
    try:
        pipeline.decrypt(encrypted_path, output_path, sidecar=sidecar)
        print(green(f"\n✓ 解密成功！"))
        print(f"  还原图片：{bold(output_path)}")
    except Exception as error:
        print(red(f"\n✗ 解密失败：{error}"))


def _default_output(input_path: str, suffix: str) -> str:
    """根据输入路径生成默认输出路径。"""
    stem, ext = os.path.splitext(input_path)
    output_ext = ".png"  # 统一输出 PNG 保证无损
    return f"{stem}{suffix}{output_ext}"


# ── 入口 ──────────────────────────────────────────────────────────────────────
def main() -> None:
    _print_banner()

    while True:
        mode = _prompt_choice(
            "选择模式：",
            choices=["encrypt", "decrypt", "exit"],
            labels=["加密图片（多层混淆）", "解密图片（逆序还原）", "退出"],
        )

        if mode == "exit":
            print(green("\n再见！"))
            break
        elif mode == "encrypt":
            try:
                _encrypt_flow()
            except KeyboardInterrupt:
                print(yellow("\n  已取消"))
        elif mode == "decrypt":
            try:
                _decrypt_flow()
            except KeyboardInterrupt:
                print(yellow("\n  已取消"))

        print()
        again = _prompt("继续操作？(y/n)", default="y").lower()
        if again != "y":
            print(green("\n再见！"))
            break


if __name__ == "__main__":
    main()
