"""
Arc Deploy - 统一部署 CLI 工具

用法：
    # 单服务项目
    arc-deploy build-and-deploy --env test

    # Monorepo 项目
    arc-deploy build-and-deploy --env test --service service-a
    arc-deploy build-and-deploy --env test --all
    arc-deploy list-services

特点：
- 配置驱动：每个服务只需一个 deploy.yaml
- 统一流程：生成标签 → 构建 → 推送 → 部署
- 简化维护：所有服务共享同一套部署逻辑
- Monorepo 支持：单个配置文件管理多个服务
"""

import json
import subprocess
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

import typer
import yaml
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.table import Table

app = typer.Typer(help="统一部署 CLI 工具")
console = Console()


def find_config_file(config_path: str) -> Path:
    """查找配置文件，支持 deploy.yaml 和 deploy.yml"""
    config_file = Path(config_path)

    # 如果文件存在，直接返回
    if config_file.exists():
        return config_file

    # 如果是默认文件名，尝试另一个扩展名
    if config_path in ("deploy.yaml", "deploy.yml"):
        alt_ext = ".yml" if config_path.endswith(".yaml") else ".yaml"
        alt_path = config_path.rsplit(".", 1)[0] + alt_ext
        alt_file = Path(alt_path)
        if alt_file.exists():
            console.print(f"[dim]使用配置文件: {alt_path}[/dim]")
            return alt_file

    # 文件不存在，返回原始路径（让调用者处理错误）
    return config_file


class DeployConfig:
    """部署配置 - 支持单服务和 Monorepo 两种格式"""

    def __init__(self, config_path: str = "deploy.yaml"):
        # 查找配置文件（支持 deploy.yaml 和 deploy.yml）
        self.config_path = find_config_file(config_path)

        if not self.config_path.exists():
            console.print(f"[red]❌ 配置文件不存在: {config_path}[/red]")
            # 如果是默认值，提示两个可能的文件名
            if config_path in ("deploy.yaml", "deploy.yml"):
                console.print(
                    "[yellow]请确保存在 deploy.yaml 或 deploy.yml 文件[/yellow]"
                )
            raise typer.Exit(1)

        with open(self.config_path) as f:
            self.config = yaml.safe_load(f)

        self._is_monorepo = "services" in self.config
        self._common_config = self.config.get("common", {})
        self._services_config = self.config.get("services", {})

    def is_monorepo(self) -> bool:
        """判断是否是 monorepo 配置"""
        return self._is_monorepo

    def get_service_names(self) -> List[str]:
        """获取所有服务名称（仅 monorepo）"""
        if not self._is_monorepo:
            return []
        return list(self._services_config.keys())

    def get_service_config(self, service_name: Optional[str] = None) -> Dict:
        """获取服务配置（合并 common 配置）"""
        if not self._is_monorepo:
            # 单服务配置，直接返回
            return self.config

        if service_name is None:
            console.print("[red]❌ Monorepo 配置必须指定 --service 参数[/red]")
            raise typer.Exit(1)

        if service_name not in self._services_config:
            console.print(f"[red]❌ 服务 '{service_name}' 不存在于配置文件中[/red]")
            console.print(
                f"[yellow]可用服务: {', '.join(self.get_service_names())}[/yellow]"
            )
            raise typer.Exit(1)

        # 合并 common 和 service 配置
        service_config = self._services_config[service_name].copy()
        merged_config = {**self._common_config, **service_config}

        # 如果未指定 service_name，使用 key 作为默认值
        if "service_name" not in merged_config or not merged_config.get("service_name"):
            merged_config["service_name"] = service_name

        # 合并嵌套的 build 配置
        if "build" in self._common_config and "build" in service_config:
            merged_config["build"] = {
                **self._common_config.get("build", {}),
                **service_config.get("build", {}),
            }

        return merged_config

    def get(self, key: str, default=None):
        """兼容旧接口（单服务配置）"""
        return self.config.get(key, default)


class DeployService:
    """部署服务"""

    def __init__(
        self, service_config: Dict, environment: str, service_id: Optional[str] = None
    ):
        """
        Args:
            service_config: 服务配置字典（已合并 common 配置）
            environment: 部署环境
            service_id: 服务 ID（用于日志显示，仅 monorepo）
        """
        self.service_config = service_config
        self.environment = environment
        self.service_id = service_id  # monorepo 中的服务 ID

        # 基础配置
        # service_name 如果未指定，使用 service_id（key）作为默认值（仅 monorepo）
        self.service_name = service_config.get("service_name") or (
            service_id if service_id else None
        )
        self.image_name = service_config.get("image_name", self.service_name)
        # ecr_registry 和 aws_region 使用默认值，不是必需的
        self.ecr_registry = service_config.get(
            "ecr_registry", "264778582408.dkr.ecr.us-west-2.amazonaws.com"
        )
        self.aws_region = service_config.get("aws_region", "us-west-2")
        self.deploy_controller_url = service_config.get("deploy_controller_url")

        # 构建配置
        self.build_config = service_config.get("build", {})

        # 验证必需配置（ecr_registry 和 aws_region 不是必需的，有默认值）
        if not all([self.service_name, self.deploy_controller_url]):
            console.print("[red]❌ 配置不完整，请检查 deploy.yaml[/red]")
            console.print(
                f"[yellow]缺少的配置: service_name={self.service_name}, deploy_controller_url={self.deploy_controller_url}[/yellow]"
            )
            raise typer.Exit(1)

    def run_command(
        self, cmd: str, description: str = "", show_output: bool = False
    ) -> str:
        """运行 shell 命令

        Args:
            cmd: 要执行的命令
            description: 命令描述
            show_output: 是否实时显示输出（用于 docker build 等需要看到过程的命令）
        """
        if description:
            console.print(f"[blue]▶ {description}[/blue]")

        try:
            if show_output:
                # 实时显示输出，不捕获
                result = subprocess.run(cmd, shell=True, check=True)
                return ""
            else:
                # 捕获输出（用于需要返回结果的命令）
                result = subprocess.run(
                    cmd, shell=True, check=True, capture_output=True, text=True
                )
                return result.stdout.strip()
        except subprocess.CalledProcessError as e:
            console.print(f"[red]❌ 命令失败: {cmd}[/red]")
            if not show_output and e.stderr:
                console.print(f"[red]错误: {e.stderr}[/red]")
            raise typer.Exit(1)

    def generate_image_tag(self) -> str:
        """生成唯一镜像标签"""
        short_sha = self.run_command("git rev-parse --short=7 HEAD", "获取 Git SHA")
        timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
        tag = f"{self.environment}-{short_sha}-{timestamp}"
        console.print(f"[green]✓ 生成标签: {tag}[/green]")
        return tag

    def build_docker_image(self, image_tag: str) -> str:
        """构建 Docker 镜像"""
        full_image = f"{self.ecr_registry}/{self.image_name}:{image_tag}"

        # 构建 Dockerfile 路径
        dockerfile = self.build_config.get("dockerfile", "Dockerfile")

        # 构建参数
        build_args = self.build_config.get("build_args", {})
        build_args_str = " ".join(
            [f"--build-arg {k}={v}" for k, v in build_args.items()]
        )

        # 构建上下文
        context = self.build_config.get("context", ".")

        # 构建命令（设置默认平台为 linux/amd64，支持在 Mac 上构建）
        cmd = f"DOCKER_DEFAULT_PLATFORM=linux/amd64 docker build --platform linux/amd64 -t {full_image} -f {dockerfile} {build_args_str} {context}"

        console.print(f"[yellow]🔨 构建镜像: {full_image}[/yellow]")
        console.print("[dim]平台: linux/amd64[/dim]")
        self.run_command(cmd, "执行 docker build", show_output=True)
        console.print("[green]✓ 镜像构建成功[/green]")

        return full_image

    def push_to_ecr(self, full_image: str):
        """推送镜像到 ECR"""
        console.print("[yellow]📤 推送镜像到 ECR...[/yellow]")

        # ECR 登录（使用实例变量中的 aws_region，已有默认值）
        login_cmd = f"aws ecr get-login-password --region {self.aws_region} | docker login --username AWS --password-stdin {self.ecr_registry}"
        self.run_command(login_cmd, "登录 ECR", show_output=True)

        # 推送镜像（实时显示推送进度）
        self.run_command(f"docker push {full_image}", "推送镜像", show_output=True)
        console.print("[green]✓ 镜像推送成功[/green]")

    def trigger_deployment(self, image_tag: str) -> str:
        """触发部署"""
        console.print(f"[yellow]🚀 触发部署到 {self.environment} 环境...[/yellow]")

        payload = {
            "service": self.service_name,
            "environment": self.environment,
            "image_tag": image_tag,
            "triggered_by": "arc-deploy",
        }

        cmd = f"""
        curl -X POST {self.deploy_controller_url}/api/webhooks/deploy \
            -H "Authorization: Bearer $WEBHOOK_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{json.dumps(payload)}' -s
        """

        response = self.run_command(cmd, "调用 webhook API")

        try:
            data = json.loads(response)
            deployment_id = data.get("deployment_id")
            if not deployment_id:
                console.print(f"[red]❌ 部署触发失败: {response}[/red]")
                raise typer.Exit(1)

            console.print(f"[green]✓ 部署已触发，ID: {deployment_id}[/green]")
            return deployment_id
        except json.JSONDecodeError:
            console.print(f"[red]❌ 响应解析失败: {response}[/red]")
            raise typer.Exit(1)

    def monitor_deployment(self, deployment_id: str, max_attempts: int = 60):
        """监控部署状态"""
        console.print("[blue]⏳ 监控部署进度...[/blue]")

        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=console,
        ) as progress:
            task = progress.add_task("等待部署完成...", total=max_attempts)

            for i in range(max_attempts):
                cmd = f"""
                curl -s {self.deploy_controller_url}/api/deployments/{deployment_id} \
                    -H "Authorization: Bearer $JWT_TOKEN" | jq -r '.status'
                """

                status = self.run_command(cmd, "")

                if status == "success":
                    progress.update(task, completed=max_attempts)
                    console.print("[green]✅ 部署成功！[/green]")
                    return

                elif status == "failed":
                    console.print("[red]❌ 部署失败[/red]")
                    # 获取错误信息
                    error_cmd = f"""
                    curl -s {self.deploy_controller_url}/api/deployments/{deployment_id} \
                        -H "Authorization: Bearer $JWT_TOKEN" | jq -r '.error_message'
                    """
                    error = self.run_command(error_cmd, "")
                    console.print(f"[red]错误: {error}[/red]")
                    raise typer.Exit(1)

                progress.update(
                    task,
                    advance=1,
                    description=f"[{i + 1}/{max_attempts}] 状态: {status}",
                )
                time.sleep(10)

            console.print("[red]⏰ 部署超时[/red]")
            raise typer.Exit(1)

    def cleanup(self, full_image: str):
        """清理本地镜像"""
        try:
            self.run_command(f"docker rmi {full_image}", "")
            console.print("[dim]✓ 清理本地镜像[/dim]")
        except:
            pass


def _deploy_single_service(
    deploy_config: DeployConfig,
    service_id: Optional[str],
    env: str,
    skip_push: bool,
    skip_deploy: bool,
) -> bool:
    """部署单个服务，返回是否成功"""
    try:
        # 获取服务配置
        service_config = deploy_config.get_service_config(service_id)
        service = DeployService(service_config, env, service_id)

        # 显示服务信息
        display_name = service_id if service_id else service.service_name
        console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
        console.print(f"[bold cyan]📦 服务: {display_name}[/bold cyan]")
        console.print(f"[bold cyan]{'=' * 60}[/bold cyan]\n")

        # 1. 生成标签
        image_tag = service.generate_image_tag()

        # 2. 构建镜像
        full_image = service.build_docker_image(image_tag)

        if skip_push:
            console.print("\n[yellow]⚠️  跳过推送和部署[/yellow]")
            return True

        # 3. 推送到 ECR
        service.push_to_ecr(full_image)

        if skip_deploy:
            console.print("\n[yellow]⚠️  跳过部署[/yellow]")
            service.cleanup(full_image)
            return True

        # 4. 触发部署
        deployment_id = service.trigger_deployment(image_tag)

        # 5. 监控部署
        service.monitor_deployment(deployment_id)

        # 6. 清理
        service.cleanup(full_image)

        # 成功
        console.print(f"\n[bold green]✅ {display_name} 部署成功！[/bold green]")
        console.print(f"[bold green]环境: {env} | 标签: {image_tag}[/bold green]\n")
        return True

    except Exception as e:
        console.print(f"\n[red]❌ {service_id or '服务'} 部署失败: {e}[/red]\n")
        return False


@app.command()
def build_and_deploy(
    env: str = typer.Option(..., "--env", "-e", help="部署环境 (test/prod)"),
    config: str = typer.Option("deploy.yaml", "--config", "-c", help="配置文件路径"),
    service: Optional[List[str]] = typer.Option(
        None, "--service", "-s", help="服务名称（可多次指定或逗号分隔，仅 monorepo）"
    ),
    all_services: bool = typer.Option(
        False, "--all", help="部署所有服务（仅 monorepo）"
    ),
    skip_push: bool = typer.Option(False, "--skip-push", help="跳过推送镜像（仅构建）"),
    skip_deploy: bool = typer.Option(
        False, "--skip-deploy", help="跳过部署（仅构建和推送）"
    ),
):
    """构建并部署服务（支持单服务和 monorepo）"""

    console.print("\n[bold blue]" + "=" * 60 + "[/bold blue]")
    console.print(f"[bold blue]🚀 开始部署流程 - {env.upper()} 环境[/bold blue]")
    console.print("[bold blue]" + "=" * 60 + "[/bold blue]\n")

    try:
        # 加载配置
        deploy_config = DeployConfig(config)

        # 确定要部署的服务列表
        services_to_deploy: List[Optional[str]] = []

        if deploy_config.is_monorepo():
            # Monorepo 配置
            if all_services:
                services_to_deploy = deploy_config.get_service_names()
                console.print(
                    f"[blue]📋 将部署所有服务: {', '.join(services_to_deploy)}[/blue]\n"
                )
            elif service:
                # 展开逗号分隔的服务名称
                for svc in service:
                    services_to_deploy.extend([s.strip() for s in svc.split(",")])
                console.print(
                    f"[blue]📋 将部署服务: {', '.join(services_to_deploy)}[/blue]\n"
                )
            else:
                console.print(
                    "[red]❌ Monorepo 配置必须指定 --service 或 --all 参数[/red]"
                )
                console.print(
                    f"[yellow]可用服务: {', '.join(deploy_config.get_service_names())}[/yellow]"
                )
                raise typer.Exit(1)
        else:
            # 单服务配置
            if service or all_services:
                console.print(
                    "[yellow]⚠️  单服务配置忽略 --service 和 --all 参数[/yellow]\n"
                )
            services_to_deploy = [None]  # None 表示使用根配置

        # 部署每个服务
        results = {}
        for svc_id in services_to_deploy:
            success = _deploy_single_service(
                deploy_config, svc_id, env, skip_push, skip_deploy
            )
            results[svc_id or "service"] = success

        # 总结
        console.print("\n[bold blue]" + "=" * 60 + "[/bold blue]")
        console.print("[bold blue]📊 部署总结[/bold blue]")
        console.print("[bold blue]" + "=" * 60 + "[/bold blue]\n")

        success_count = sum(results.values())
        total_count = len(results)

        for svc_id, success in results.items():
            status = "[green]✅ 成功[/green]" if success else "[red]❌ 失败[/red]"
            console.print(f"  {svc_id}: {status}")

        console.print(f"\n[bold]总计: {success_count}/{total_count} 成功[/bold]")
        console.print("[bold blue]" + "=" * 60 + "[/bold blue]\n")

        # 如果有失败的服务，返回错误码
        if success_count < total_count:
            raise typer.Exit(1)

    except typer.Exit:
        raise
    except Exception as e:
        console.print(f"\n[red]❌ 部署过程出错: {e}[/red]")
        raise typer.Exit(1)


@app.command()
def build_only(
    config: str = typer.Option("deploy.yaml", "--config", "-c", help="配置文件路径"),
    service_name: Optional[str] = typer.Option(
        None, "--service", "-s", help="服务名称（仅 monorepo）"
    ),
    tag: Optional[str] = typer.Option(None, "--tag", "-t", help="自定义标签（可选）"),
):
    """仅构建 Docker 镜像（不推送、不部署）"""

    deploy_config = DeployConfig(config)

    # Monorepo 需要指定 service
    if deploy_config.is_monorepo() and not service_name:
        console.print("[red]❌ Monorepo 配置必须指定 --service 参数[/red]")
        console.print(
            f"[yellow]可用服务: {', '.join(deploy_config.get_service_names())}[/yellow]"
        )
        raise typer.Exit(1)

    service_config = deploy_config.get_service_config(service_name)
    service = DeployService(service_config, "local", service_name)

    if not tag:
        tag = service.generate_image_tag()

    full_image = service.build_docker_image(tag)
    console.print(f"\n[green]✅ 镜像构建完成: {full_image}[/green]")


@app.command()
def validate_config(
    config: str = typer.Option("deploy.yaml", "--config", "-c", help="配置文件路径"),
):
    """验证配置文件"""

    console.print("[blue]🔍 验证配置文件...[/blue]\n")

    try:
        deploy_config = DeployConfig(config)

        if deploy_config.is_monorepo():
            # Monorepo 配置验证
            console.print("[bold]配置类型:[/bold] Monorepo\n")

            # 验证 common 配置
            common = deploy_config._common_config
            console.print("[bold]Common 配置:[/bold]")
            ecr_registry = common.get(
                "ecr_registry", "264778582408.dkr.ecr.us-west-2.amazonaws.com"
            )
            aws_region = common.get("aws_region", "us-west-2")
            ecr_display = f"{ecr_registry} [dim](默认)[/dim]" if not common.get("ecr_registry") else ecr_registry
            region_display = f"{aws_region} [dim](默认)[/dim]" if not common.get("aws_region") else aws_region
            console.print(f"  ECR Registry: {ecr_display}")
            console.print(f"  Deploy Controller: {common.get('deploy_controller_url')}")
            console.print(f"  AWS Region: {region_display}")

            # 验证每个服务
            services = deploy_config.get_service_names()
            console.print(f"\n[bold]服务列表 ({len(services)}):[/bold]")

            all_valid = True
            for svc_name in services:
                svc_config = deploy_config.get_service_config(svc_name)
                # service_name 如果未指定，会使用 key 作为默认值
                service_name = svc_config.get("service_name", svc_name)
                image_name = svc_config.get("image_name", service_name)

                # 显示服务配置（如果 service_name 与 key 相同，显示为默认值）
                if service_name == svc_name:
                    display_name = f"{service_name} [dim](默认，与 key 相同)[/dim]"
                else:
                    display_name = service_name

                console.print(f"  [green]✓[/green] {svc_name}: {display_name}")

            if all_valid:
                console.print("\n[green]✅ 所有配置有效[/green]")
            else:
                console.print("\n[red]❌ 部分配置无效[/red]")
                raise typer.Exit(1)

        else:
            # 单服务配置验证
            console.print("[bold]配置类型:[/bold] 单服务\n")

            required_fields = ["service_name", "deploy_controller_url"]
            missing_fields = [f for f in required_fields if not deploy_config.get(f)]

            if missing_fields:
                console.print(
                    f"[red]❌ 缺少必需字段: {', '.join(missing_fields)}[/red]"
                )
                raise typer.Exit(1)

            console.print("[bold]配置摘要：[/bold]")
            console.print(f"  服务名称: {deploy_config.get('service_name')}")
            console.print(f"  镜像名称: {deploy_config.get('image_name')}")
            ecr_registry = deploy_config.get(
                "ecr_registry", "264778582408.dkr.ecr.us-west-2.amazonaws.com"
            )
            aws_region = deploy_config.get("aws_region", "us-west-2")
            console.print(f"  ECR 仓库: {ecr_registry} [dim](默认)[/dim]" if not deploy_config.get("ecr_registry") else f"  ECR 仓库: {ecr_registry}")
            console.print(f"  AWS Region: {aws_region} [dim](默认)[/dim]" if not deploy_config.get("aws_region") else f"  AWS Region: {aws_region}")
            console.print(
                f"  Deploy Controller: {deploy_config.get('deploy_controller_url')}"
            )

            build_config = deploy_config.get("build", {})
            if build_config:
                console.print("\n[bold]构建配置：[/bold]")
                console.print(
                    f"  Dockerfile: {build_config.get('dockerfile', 'Dockerfile')}"
                )
                console.print(f"  Context: {build_config.get('context', '.')}")
                if build_config.get("build_args"):
                    console.print("  Build Args:")
                    for k, v in build_config["build_args"].items():
                        console.print(f"    {k}: {v}")

            console.print("\n[green]✅ 配置有效[/green]")

    except typer.Exit:
        raise
    except Exception as e:
        console.print(f"[red]❌ 配置验证失败: {e}[/red]")
        raise typer.Exit(1)


@app.command()
def list_services(
    config: str = typer.Option("deploy.yaml", "--config", "-c", help="配置文件路径"),
):
    """列出 monorepo 中的所有服务"""

    try:
        deploy_config = DeployConfig(config)

        if not deploy_config.is_monorepo():
            console.print("[yellow]⚠️  单服务配置，无需列出服务[/yellow]")
            console.print(f"服务名称: {deploy_config.get('service_name')}")
            return

        services = deploy_config.get_service_names()

        # 创建表格
        table = Table(title=f"Monorepo 服务列表 ({len(services)})", show_header=True)
        table.add_column("服务 ID", style="cyan", no_wrap=True)
        table.add_column("服务名称", style="green")
        table.add_column("镜像名称", style="yellow")
        table.add_column("Dockerfile", style="blue")

        for svc_id in services:
            svc_config = deploy_config.get_service_config(svc_id)
            service_name = svc_config.get("service_name", "[dim]未配置[/dim]")
            image_name = svc_config.get("image_name", service_name)
            dockerfile = svc_config.get("build", {}).get("dockerfile", "Dockerfile")

            table.add_row(svc_id, service_name, image_name, dockerfile)

        console.print(table)

        # 显示用法提示
        console.print("\n[bold]使用示例：[/bold]")
        console.print("  # 部署单个服务")
        console.print(
            f"  arc-deploy build-and-deploy --env test --service {services[0]}"
        )
        console.print("\n  # 部署多个服务")
        console.print(
            f"  arc-deploy build-and-deploy --env test --service {services[0]},{services[1] if len(services) > 1 else services[0]}"
        )
        console.print("\n  # 部署所有服务")
        console.print("  arc-deploy build-and-deploy --env test --all")

    except Exception as e:
        console.print(f"[red]❌ 错误: {e}[/red]")
        raise typer.Exit(1)


def cli():
    """CLI entry point for arc-deploy command"""
    app()


if __name__ == "__main__":
    cli()
