#!python3
# coding=utf-8

from openai import OpenAI
import fire
import subprocess
import sys
import signal
import tty
import termios
import httpx
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.syntax import Syntax
from rich import box
from rich.markdown import Markdown

_VERSION_ = "2.0.0"
_MODEL_ = "doubao-seed-2-0-code-preview-260215"
_BASE_URL_ = "https://ark.cn-beijing.volces.com/api/v3"
_API_KEY_ = "0fb5c683-9002-4bca-9811-b81f46d07e9c"

# 初始化 Rich Console
console = Console()

# 设置信号处理，确保 Ctrl+C 能够退出
def signal_handler(sig, frame):
    console.print("\n[yellow]程序已中断[/yellow]")
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

# 初始化OpenAI客户端
client = OpenAI(
    base_url=_BASE_URL_,
    api_key=_API_KEY_,
    http_client=httpx.Client(verify=False, timeout=60.0),
)


def getch():
    """获取单个字符输入，无需按回车"""
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        ch = sys.stdin.read(1)
        if ord(ch) == 3:  # Ctrl+C
            raise KeyboardInterrupt
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

prompt_zh = """你是一位专业的 Git 提交消息生成助手。请根据提供的代码变更内容,生成简洁、清晰的提交消息。

<requirements>
1. 使用中文描述
2. 采用约定式提交格式:<类型>: <描述> 或 <类型>(<作用域>): <描述>
3. 类型包括:feat(新功能)、fix(修复)、docs(文档)、style(格式)、refactor(重构)、test(测试)、chore(构建/工具)
4. 如果用户指定了作用域(scope),必须在类型后添加括号包裹的作用域;如果用户明确禁用了作用域,则不要添加作用域
5. 描述要简洁明了,突出核心变更和主要改动
6. 即使有多个文件变更,也要生成一条统一的提交消息,概括主要变更
7. 如果要求生成多条消息,每条消息用换行符分隔,不要添加序号或其他标记
8. 不要添加任何解释,只返回提交消息
</requirements>

<examples>
feat: 添加用户登录功能
feat(auth): 添加用户登录功能
fix: 修复首页加载异常问题
fix(homepage): 修复首页加载异常问题
refactor: 优化数据库查询性能和缓存机制
refactor(database): 优化数据库查询性能和缓存机制
</examples>"""

prompt_en = """You are a professional Git commit message generator. Generate concise and clear commit messages based on the provided code changes.

<requirements>
1. Use English for descriptions
2. Follow conventional commit format: <type>: <description> or <type>(<scope>): <description>
3. Types include: feat (new feature), fix (bug fix), docs (documentation), style (formatting), refactor (code refactoring), test (testing), chore (build/tooling)
4. If user specifies a scope, you MUST include it in parentheses after the type; if user explicitly disables scope, do NOT add scope
5. Keep descriptions concise and highlight the main change
6. Even if there are multiple file changes, generate ONE unified commit message that summarizes the primary change
7. If asked to generate multiple messages, separate each message with a newline, without numbering or other markers
8. Return only commit messages, do not add any explanations
</requirements>

<examples>
feat: add user authentication system
feat(auth): add user authentication system
fix: resolve homepage loading issue
fix(homepage): resolve homepage loading issue
refactor: optimize database query and caching mechanism
refactor(database): optimize database query and caching mechanism
</examples>"""


def get_git_diff():
    """
    获取 git 暂存区的文件变更
    :return: git diff 内容
    """
    try:
        # 获取暂存区的 diff
        result = subprocess.run(
            ['git', 'diff', '--cached'],
            capture_output=True,
            text=True,
            check=True
        )
        
        if not result.stdout.strip():
            # 如果暂存区为空，检查是否有未暂存的文件
            unstaged_result = subprocess.run(
                ['git', 'diff', '--name-only'],
                capture_output=True,
                text=True,
                check=True
            )
            
            untracked_result = subprocess.run(
                ['git', 'ls-files', '--others', '--exclude-standard'],
                capture_output=True,
                text=True,
                check=True
            )
            
            has_changes = unstaged_result.stdout.strip() or untracked_result.stdout.strip()
            
            if has_changes:
                console.print("[yellow]⚠ 暂存区为空，但检测到未暂存的文件变更[/yellow]")
                try:
                    console.print("是否执行 [cyan]'git add -A'[/cyan] 将所有变更添加到暂存区？[Y/n]: ", end="")
                    choice = getch().lower()
                    console.print(choice)
                    
                    if choice in ['y', '\r', '\n', '']:
                        try:
                            subprocess.run(['git', 'add', '-A'], check=True)
                            console.print("[green]✓ 已添加所有变更到暂存区[/green]")
                            # 重新获取 diff
                            result = subprocess.run(
                                ['git', 'diff', '--cached'],
                                capture_output=True,
                                text=True,
                                check=True
                            )
                            return result.stdout
                        except subprocess.CalledProcessError as e:
                            console.print(f"[red]✗ 执行 git add 失败: {e}[/red]")
                            return None
                    elif choice == 'n':
                        console.print("[yellow]已取消[/yellow]")
                        sys.exit(0)
                    else:
                        console.print("[red]无效输入，已取消[/red]")
                        sys.exit(0)
                except KeyboardInterrupt:
                    console.print("\n[yellow]程序已中断[/yellow]")
                    sys.exit(0)
            else:
                console.print("[yellow]暂存区为空，且没有检测到文件变更[/yellow]")
                return None
            
        return result.stdout
    except subprocess.CalledProcessError as e:
        if "not a git repository" in (e.stderr or ""):
            console.print("[red]✗ 当前目录不是 Git 仓库，请先进入项目目录再运行[/red]")
        else:
            console.print(f"[red]✗ 获取 git diff 失败: {e}[/red]")
        return None
    except FileNotFoundError:
        console.print("[red]✗ 未找到 git 命令，请确保已安装 git[/red]")
        return None


def generate_commit_message(diff_content, language='en', context='', num=1, scope='', no_scope=False):
    """
    使用 AI API 生成提交消息
    :param diff_content: git diff 内容
    :param language: 语言选择,'zh' 或 'en',默认 'en'
    :param context: 额外的上下文信息,用于补充说明变更的目的或重点
    :param num: 生成的提交消息数量,默认 1
    :param scope: 提交消息的作用域(scope),例如 'workflow', 'auth' 等
    :param no_scope: 是否禁止生成 scope,默认 False
    :return: 生成的提交消息列表
    """
    try:
        # 根据语言选择prompt和提示文本
        if language == 'zh':
            prompt = prompt_zh
            if num > 1:
                instruction = f"请根据以下代码变更生成 {num} 条不同的提交消息，每条消息用换行符分隔："
            else:
                instruction = "请根据以下代码变更生成提交消息："
        else:
            prompt = prompt_en
            if num > 1:
                instruction = f"Please generate {num} different commit messages based on the following code changes, separate each message with a newline:"
            else:
                instruction = "Please generate a commit message based on the following code changes:"
        
        # 构建用户消息
        user_message = f"""{instruction}

<git_diff>
{diff_content}
</git_diff>"""
        
        # 如果禁用了作用域,明确告诉 AI 不要生成 scope
        if no_scope:
            if language == 'zh':
                user_message += f"\n\n<scope_requirement>\n严禁使用作用域！\n提交消息格式必须为:<类型>: <描述>\n不要在类型后添加括号和作用域！\n</scope_requirement>"
            else:
                user_message += f"\n\n<scope_requirement>\nYou MUST NOT use scope!\nCommit message format MUST be: <type>: <description>\nDo NOT add parentheses and scope after the type!\n</scope_requirement>"
        # 如果有作用域信息,添加到消息中
        elif scope:
            if language == 'zh':
                user_message += f"\n\n<scope_requirement>\n必须使用作用域:{scope}\n提交消息格式必须为:<类型>({scope}): <描述>\n</scope_requirement>"
            else:
                user_message += f"\n\n<scope_requirement>\nYou MUST use scope: {scope}\nCommit message format MUST be: <type>({scope}): <description>\n</scope_requirement>"
        
        # 如果有额外的上下文信息,添加到消息中
        if context:
            if language == 'zh':
                user_message += f"\n\n<additional_context>\n用户补充说明:{context}\n</additional_context>"
            else:
                user_message += f"\n\n<additional_context>\nUser notes: {context}\n</additional_context>"
        
        completion = client.chat.completions.create(
            model=_MODEL_,
            messages=[
                {"role": "system", "content": prompt},
                {"role": "user", "content": user_message},
            ],
        )
        
        result = completion.choices[0].message.content
        
        # 如果生成多条消息，按行分割
        if num > 1:
            messages = [msg.strip() for msg in result.strip().split('\n') if msg.strip()]
            return messages
        else:
            return [result.strip()]
            
    except Exception as e:
        console.print(f"[red]✗ 生成提交消息失败: {type(e).__name__}: {str(e)}[/red]")
        return None


def select_commit_message(commit_messages):
    """
    从多条提交消息中选择一条
    :param commit_messages: 提交消息列表
    :return: 选择的提交消息，如果取消则返回 None
    """
    if len(commit_messages) == 1:
        # 只有一条消息，直接显示
        selected_message = commit_messages[0]
        panel = Panel(
            selected_message,
            title="[bold cyan]生成的提交消息[/bold cyan]",
            border_style="cyan",
            box=box.ROUNDED
        )
        console.print(panel)
        return selected_message
    else:
        # 多条消息，显示表格供用户选择
        table = Table(
            title="[bold cyan]生成的提交消息[/bold cyan]",
            box=box.ROUNDED,
            show_header=True,
            header_style="bold magenta"
        )
        table.add_column("#", style="cyan", width=4, justify="center")
        table.add_column("提交消息", style="white")
        
        for i, msg in enumerate(commit_messages, 1):
            table.add_row(str(i), msg)
        
        console.print(table)
        
        # 让用户选择
        while True:
            try:
                console.print(f"\n请选择要使用的提交消息 [cyan][1-{len(commit_messages)}][/cyan] 或按 [cyan]n[/cyan] 取消: ", end="")
                choice = getch().lower()
                console.print(choice)
                
                if choice == 'n':
                    console.print("[yellow]已取消提交[/yellow]")
                    return None
                
                if choice.isdigit():
                    index = int(choice) - 1
                    if 0 <= index < len(commit_messages):
                        selected_message = commit_messages[index]
                        console.print(f"\n[green]✓ 已选择:[/green] [bold]{selected_message}[/bold]\n")
                        return selected_message
                
                console.print(f"[red]无效输入，请输入 1 到 {len(commit_messages)} 之间的数字或 n[/red]")
            except KeyboardInterrupt:
                console.print("\n[yellow]程序已中断[/yellow]")
                sys.exit(0)


def commit_with_message(selected_message, auto_commit=False):
    """
    使用选定的消息执行提交
    :param selected_message: 提交消息
    :param auto_commit: 是否自动提交，不询问用户
    :return: True 如果提交成功，False 如果取消或失败
    """
    if auto_commit:
        # 自动提交模式
        try:
            subprocess.run(
                ['git', 'commit', '-m', selected_message],
                check=True
            )
            console.print("[bold green]✓ 提交成功！[/bold green]")
            return True
        except subprocess.CalledProcessError as e:
            console.print(f"[red]✗ 提交失败: {e}[/red]")
            return False
    else:
        # 询问用户是否提交
        try:
            console.print("是否使用此消息提交？[Y/n]: ", end="")
            choice = getch().lower()
            console.print(choice)
            
            if choice in ['y', '\r', '\n', '']:
                try:
                    subprocess.run(
                        ['git', 'commit', '-m', selected_message],
                        check=True
                    )
                    console.print("[bold green]✓ 提交成功！[/bold green]")
                    return True
                except subprocess.CalledProcessError as e:
                    console.print(f"[red]✗ 提交失败: {e}[/red]")
                    return False
            elif choice == 'n':
                console.print("[yellow]已取消提交[/yellow]")
                return False
            else:
                console.print("[red]无效输入，已取消[/red]")
                return False
        except KeyboardInterrupt:
            console.print("\n[yellow]程序已中断[/yellow]")
            sys.exit(0)


def push_after_commit():
    """执行 git push"""
    try:
        subprocess.run(['git', 'push'], check=True)
        console.print("[bold green]✓ 推送成功！[/bold green]")
        return True
    except subprocess.CalledProcessError as e:
        console.print(f"[red]✗ 推送失败: {e}[/red]")
        return False


def show_documentation():
    """显示使用文档"""
    # 标题
    console.print(Panel.fit(
        "[bold cyan]Git 提交消息生成助手 - 使用文档[/bold cyan]",
        border_style="cyan"
    ))
    
    # 简介
    console.print("\n[bold]📖 简介[/bold]")
    console.print("智能生成规范的 Git 提交消息，支持中英文，基于 AI 分析代码变更。\n")
    
    # 基础用法
    console.print("[bold]🚀 基础用法[/bold]")
    usage_table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
    usage_table.add_column(style="cyan")
    usage_table.add_column(style="white")
    usage_table.add_row("python git-committor.py", "生成英文提交消息（交互模式）")
    usage_table.add_row("python git-committor.py --lang=zh", "生成中文提交消息")
    usage_table.add_row("python git-committor.py --auto-commit", "自动提交，不询问确认")
    usage_table.add_row("python git-committor.py -p", "自动提交并执行 git push")
    usage_table.add_row("python git-committor.py --num=3", "生成 3 条候选消息供选择")
    console.print(usage_table)
    console.print()
    
    # 参数说明
    console.print("[bold]⚙️  参数说明[/bold]")
    param_table = Table(box=box.ROUNDED, show_header=True, header_style="bold magenta")
    param_table.add_column("参数", style="cyan", width=20)
    param_table.add_column("类型", style="yellow", width=10)
    param_table.add_column("默认值", style="green", width=10)
    param_table.add_column("说明", style="white")
    
    param_table.add_row(
        "--context",
        "string",
        "''",
        "补充说明变更的目的或重点"
    )
    param_table.add_row(
        "--scope",
        "string",
        "''",
        "指定提交消息的作用域，如 'workflow', 'auth' 等"
    )
    param_table.add_row(
        "--no-scope",
        "bool",
        "False",
        "禁用作用域，强制生成不带 scope 的提交消息"
    )
    param_table.add_row(
        "--auto-commit",
        "bool",
        "False",
        "自动提交，不询问用户确认"
    )
    param_table.add_row(
        "--lang",
        "string",
        "'en'",
        "消息语言：'zh'(中文) 或 'en'(英文)"
    )
    param_table.add_row(
        "--num",
        "int",
        "1",
        "生成的候选消息数量（1-9）"
    )
    param_table.add_row(
        "--help",
        "bool",
        "False",
        "显示此帮助文档"
    )
    param_table.add_row(
        "-y, --y",
        "bool",
        "False",
        "自动提交，不询问用户确认"
    )
    param_table.add_row(
        "-p, --p",
        "bool",
        "False",
        "自动提交并执行 git push"
    )
    console.print(param_table)
    console.print()
    
    # 示例
    console.print("[bold]💡 使用示例[/bold]")
    examples = [
        ("基础使用", "python git-committor.py"),
        ("中文消息 + 3个候选", "python git-committor.py --lang=zh --num=3"),
        ("添加上下文说明", "python git-committor.py --context='修复生产环境bug'"),
        ("指定作用域", "python git-committor.py --scope=workflow"),
        ("禁用作用域", "python git-committor.py --no-scope"),
        ("自动提交模式", "python git-committor.py --lang=zh --auto-commit"),
        ("自动提交（简写）", "python git-committor.py -y"),
        ("自动提交并推送", "python git-committor.py -p"),
        ("完整示例", "python git-committor.py --lang=zh --num=3 --scope=workflow --context='优化性能'"),
    ]
    
    for title, cmd in examples:
        console.print(f"  [dim]#{title}[/dim]")
        console.print(f"  [green]{cmd}[/green]\n")
    
    # 提交消息格式
    console.print("[bold]📝 提交消息格式[/bold]")
    format_table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
    format_table.add_column(style="cyan bold", width=12)
    format_table.add_column(style="white")
    format_table.add_row("feat:", "新增功能")
    format_table.add_row("fix:", "修复问题")
    format_table.add_row("docs:", "文档更新")
    format_table.add_row("style:", "代码格式调整")
    format_table.add_row("refactor:", "代码重构")
    format_table.add_row("test:", "测试相关")
    format_table.add_row("chore:", "构建/工具变动")
    console.print(format_table)
    console.print()
    
    # 版本信息
    console.print(Panel.fit(
        f"[dim]Version {_VERSION_} | Author: @louisyoungx | ❤️[/dim]",
        border_style="dim"
    ))


def main(context='', scope='', no_scope=False, auto_commit=False, lang='en', num=1, help=False, y=False, p=False):
    """
    主函数：生成 git 提交消息
    :param context: 额外的上下文信息，用于补充说明变更的目的或重点
    :param scope: 提交消息的作用域(scope)，例如 'workflow', 'auth' 等
    :param no_scope: 禁用作用域，明确要求 AI 不生成带 scope 的提交消息，默认 False
    :param auto_commit: 是否自动执行 git commit，默认 False
    :param lang: 提交消息语言，'zh' 或 'en'，默认 'en'
    :param num: 生成的提交消息数量，默认 1
    :param help: 显示使用文档，默认 False
    :param y: 自动提交，不询问用户确认，默认 False
    :param p: 自动提交并执行 git push，默认 False
    """
    # 如果请求显示文档
    if help:
        show_documentation()
        return
    
    # 显示标题
    console.print(Panel.fit(
        "[bold cyan]Git 提交消息生成助手[/bold cyan]",
        border_style="cyan"
    ))
    
    # 获取 git diff
    diff_content = get_git_diff()
    
    if not diff_content:
        return
    
    # 显示用户提供的作用域和上下文信息
    if no_scope:
        console.print(f"[dim]🚫 禁用作用域:[/dim] [yellow]不生成 scope[/yellow]")
    elif scope:
        console.print(f"[dim]🎯 作用域:[/dim] [bold cyan]{scope}[/bold cyan]")
    if context:
        console.print(f"[dim]📝 用户补充说明:[/dim] [italic]{context}[/italic]")
    if scope or context or no_scope:
        console.print()
    
    # 生成提交消息
    with Progress(
        SpinnerColumn(),
        TextColumn("[bold blue]正在生成提交消息...[/bold blue]"),
        console=console,
        transient=True
    ) as progress:
        progress.add_task("", total=None)
        commit_messages = generate_commit_message(diff_content, lang, context, num, scope, no_scope)
    
    if not commit_messages:
        return
    
    # 选择要使用的提交消息
    selected_message = select_commit_message(commit_messages)
    if not selected_message:
        return
    
    # 执行提交
    commit_success = commit_with_message(selected_message, auto_commit or y or p)

    # 如果启用 -p 且提交成功，执行推送
    if p and commit_success:
        push_after_commit()


if __name__ == '__main__':
    fire.Fire(main)
