"""
Skills 智能技能工具
根据用户需求描述或需求文件，自动匹配并执行相应的技能来完成任务
支持项目级别（.joycode/skills/）和用户级别（~/.joycode/skills/）两种技能来源

"""

import re
from pathlib import Path
from typing import List, Literal, Optional
from pydantic import BaseModel, Field

from ..utils.file_utils import file_exists, dir_exists, write_file, read_dir
from ..utils.file_reader import read_file


class SkillMetadata(BaseModel):
    """技能元数据类型"""
    name: str  # 显示名称（可能包含中文）
    description: str
    dir_name: str  # 目录名称（用于构建路径）
    location: Literal['project', 'user'] = "project"
    path: Optional[str] = None  # 完整路径（便于直接读取）


class SkillsExpertParam(BaseModel):
    """skills_expert 工具的参数结构"""
    requirements: Optional[str] = Field(
        None,
        description="用户需求描述"
    )
    requirements_file: Optional[str] = Field(
        None,
        description="需求文件相对项目根目录的路径"
    )
    repo_path: str = Field(
        ...,
        description="Git 仓库目录（绝对路径）"
    )


def get_user_home() -> str:
    """获取用户 home 目录路径"""
    return str(Path.home())


def get_user_skills_dir() -> str:
    """获取用户级别 skill 目录路径"""
    return str(Path(get_user_home()) / '.joycode' / 'skills')


def get_skill_summary_path(base_path: str, location: Literal['project', 'user'] = 'project') -> Path:
    """
    获取汇总文件路径
    
    Args:
        base_path: 基础路径（项目级为 repo_path，用户级为 user_skills_dir）
        location: 技能位置类型，用于区分路径构建逻辑

    Returns:
        汇总文件的完整路径
    """
    # 如果是用户目录，base_path 已经是 .../.joycode/skills
    if location == 'user':
        return Path(base_path) / 'SKILL.md'
    # 如果是项目目录，base_path 是项目根目录
    return Path(base_path) / '.joycode' / 'skills' / 'SKILL.md'


async def read_skill_summary(base_path: str, location: Literal['project', 'user'] = 'project') -> Optional[List[SkillMetadata]]:
    """
    读取汇总文件内容，解析为技能列表
    
    Args:
        base_path: 基础路径（项目级为 repo_path，用户级为 user_skills_dir）
        location: 技能位置类型

    Returns:
        技能元数据列表，文件不存在时返回 None
    """
    try:
        summary_path = get_skill_summary_path(base_path, location)

        if not file_exists(summary_path):
            return None
        
        content = await read_file(summary_path)
        skills = parse_skill_summary_content(content, location)

        # 补全 path 字段
        skills_dir = base_path if location == 'user' else str(Path(base_path) / '.joycode' / 'skills')
        for skill in skills:
            skill.path = str(Path(skills_dir) / skill.dir_name / 'SKILL.md')

        return skills
    except Exception:
        return None


def parse_skill_summary_content(content: str, location: Literal['project', 'user'] = 'project') -> List[SkillMetadata]:
    """
    解析汇总文件内容为技能列表
    
    Args:
        content: 汇总文件内容
        location: 技能位置

    Returns:
        技能元数据列表
    """
    skills: List[SkillMetadata] = []
    
    try:
        # 匹配所有三级标题（### skill-name）和其后的描述
        # 格式：### name|dir_name\n**来源**: project|user\n描述
        skill_pattern = r'###\s+(.+?)\n([\s\S]*?)(?=\n###|$)'
        matches = re.finditer(skill_pattern, content)
        
        for match in matches:
            title = match.group(1).strip()
            raw_description = match.group(2).strip()

            if title:
                # 检查是否包含目录名（格式：name|dir_name）
                if '|' in title:
                    name, dir_name = title.split('|', 1)
                    name = name.strip()
                    dir_name = dir_name.strip()
                else:
                    # 兼容旧格式：如果没有目录名，使用名称作为目录名（需要转换为小写和连字符）
                    name = title
                    # 尝试将中文名称转换为目录名格式
                    dir_name = name.lower().replace(' ', '-').replace('_', '-')
                    # 移除特殊字符，只保留字母、数字和连字符
                    dir_name = re.sub(r'[^a-z0-9-]', '', dir_name)
                
                # 尝试解析来源信息（格式：**来源**: project | user）
                current_loc = location
                description = raw_description

                source_match = re.match(r'^\*\*来源\*\*:\s*(project|user)\s*\n([\s\S]*)$', raw_description)
                if source_match:
                    current_loc = source_match.group(1)
                    description = source_match.group(2).strip()

                skills.append(SkillMetadata(
                    name=name,
                    description=description or '暂无描述',
                    dir_name=dir_name,
                    location=current_loc
                ))
    except Exception:
        # ignore
        pass
    
    return skills


def generate_skill_summary_content(skills: List[SkillMetadata]) -> str:
    """
    生成汇总文件的 Markdown 内容
    
    Args:
        skills: 技能元数据列表
        
    Returns:
        格式化的 Markdown 内容
    """
    content = """# Skills 汇总

本文件作为技能索引，记录项目中所有可用的技能，由系统自动维护。
通过索引机制实现技能的快速定位和高效执行。

## 技能列表

"""
    
    for skill in skills:
        # 格式：### name|dir_name\n**来源**: location\n描述
        # 这样可以在解析时同时获取显示名、目录名和来源
        content += f"### {skill.name}|{skill.dir_name}\n**来源**: {skill.location}\n{skill.description}\n\n"

    return content


async def write_skill_summary(base_path: str, skills: List[SkillMetadata], location: Literal['project', 'user'] = 'project') -> None:
    """
    将技能列表写入汇总文件
    
    Args:
        base_path: 基础路径（项目级为 repo_path，用户级为 user_skills_dir）
        skills: 技能元数据列表
        location: 技能位置类型
    """
    try:
        summary_path = get_skill_summary_path(base_path, location)
        content = generate_skill_summary_content(skills)
        
        write_file(summary_path, content)
    except Exception:
        # ignore
        pass


def parse_skill_metadata(content: str, skill_dir_name: str, location: Literal['project', 'user'] = 'project') -> Optional[SkillMetadata]:
    """
    解析SKILL.md文件，提取技能元数据
    
    Args:
        content: SKILL.md文件内容
        skill_dir_name: 技能目录名称（用于构建路径）
        location: 技能位置类型

    Returns:
        技能元数据，如果解析失败返回None
    """
    try:
        # 提取name字段（优先使用YAML前置matter中的name）
        name_match = re.search(r'^name:\s*(.+)$', content, re.MULTILINE)
        name = name_match.group(1).strip() if name_match else skill_dir_name
        
        # 提取description字段（优先使用YAML前置matter中的description）
        desc_match = re.search(r'^description:\s*(.+)$', content, re.MULTILINE)
        description = desc_match.group(1).strip() if desc_match else ''
        
        # 如果没有在YAML中找到description，尝试从内容中提取第一个段落
        if not description:
            first_paragraph_match = re.search(r'^# .+?\n\n(.+?)(?:\n\n|$)', content, re.DOTALL)
            description = first_paragraph_match.group(1).strip() if first_paragraph_match else '暂无描述'
        
        return SkillMetadata(
            name=name,
            description=description,
            dir_name=skill_dir_name,  # 保存实际的目录名
            location=location
        )
    except Exception:
        return None


async def scan_skills_directory(base_path: str, location: Literal['project', 'user'] = 'project') -> List[SkillMetadata]:
    """
    扫描 skills 目录获取所有技能
    
    Args:
        base_path: 基础路径（项目级为 repo_path，用户级为 user_skills_dir）
        location: 技能位置类型

    Returns:
        技能元数据列表
    """
    try:
        # 如果是用户模式，base_path 已经是 .../.joycode/skills
        # 如果是项目模式，base_path 是 repoPath，需要拼接 .joycode/skills
        skills_dir = Path(base_path) if location == 'user' else Path(base_path) / '.joycode' / 'skills'

        if not dir_exists(skills_dir):
            return []
        
        skill_dirs = read_dir(skills_dir)
        skills: List[SkillMetadata] = []
        
        for skill_dir_name in skill_dirs:
            # 跳过汇总文件和隐藏文件
            if skill_dir_name == 'SKILL.md' or skill_dir_name.startswith('.'):
                continue
            
            skill_path = skills_dir / skill_dir_name / 'SKILL.md'
            
            if file_exists(skill_path):
                try:
                    skill_content = await read_file(skill_path)
                    skill_info = parse_skill_metadata(skill_content, skill_dir_name, location)

                    if skill_info:
                        skill_info.path = str(skill_path)
                        skills.append(skill_info)
                except Exception:
                    # ignore
                    pass
        
        return skills
    except Exception:
        return []


async def append_skill_to_summary(base_path: str, new_skill: SkillMetadata, location: Literal['project', 'user'] = 'project') -> None:
    """
    将新发现的技能追加到汇总文件
    
    Args:
        base_path: 基础路径（项目级为 repo_path，用户级为 user_skills_dir）
        new_skill: 新技能的元数据
        location: 技能位置类型
    """
    try:
        # 读取现有汇总文件
        existing_skills = await read_skill_summary(base_path, location)

        if not existing_skills:
            existing_skills = []
        
        # 检查技能是否已存在
        exists = any(skill.name == new_skill.name for skill in existing_skills)
        
        if exists:
            return
        
        # 追加新技能
        existing_skills.append(new_skill)
        
        # 写回汇总文件
        await write_skill_summary(base_path, existing_skills, location)
    except Exception:
        # ignore
        pass


def is_skills_synchronized(summary_skills: List[SkillMetadata], actual_skill_dirs: List[str]) -> bool:
    """
    检查汇总文件中的技能列表与实际目录是否同步

    Args:
        summary_skills: 汇总文件中的技能列表
        actual_skill_dirs: 实际存在的技能目录名列表

    Returns:
        是否同步
    """
    if len(summary_skills) != len(actual_skill_dirs):
        return False

    # 检查每个实际存在的目录是否都在汇总文件中
    summary_skill_names = {s.dir_name for s in summary_skills}
    for dir_name in actual_skill_dirs:
        if dir_name not in summary_skill_names:
            return False

    return True


def merge_skills(
    project_skills: List[SkillMetadata],
    user_skills: List[SkillMetadata]
) -> List[SkillMetadata]:
    """
    合并两个级别的 skills，项目级优先覆盖同名用户级

    Args:
        project_skills: 项目级别技能
        user_skills: 用户级别技能

    Returns:
        合并后的技能列表
    """
    merged_map: dict[str, SkillMetadata] = {}

    # 先添加用户级技能
    for skill in user_skills:
        merged_map[skill.name] = skill

    # 再添加项目级技能（覆盖同名用户级技能）
    for skill in project_skills:
        merged_map[skill.name] = skill

    return list(merged_map.values())


async def get_project_skills(repo_path: str) -> List[SkillMetadata]:
    """
    获取项目级别技能

    Args:
        repo_path: 项目根目录路径
        
    Returns:
        项目级技能元数据列表
    """
    try:
        # 步骤1: 检查汇总文件是否存在
        summary_skills = await read_skill_summary(repo_path, 'project')

        # 步骤2: 获取物理目录下的技能列表，用于同步检查
        skills_dir = Path(repo_path) / '.joycode' / 'skills'
        skill_dirs: List[str] = []
        if dir_exists(skills_dir):
            entries = read_dir(skills_dir)
            skill_dirs = [d for d in entries if d != 'SKILL.md' and not d.startswith('.')]
        
        if summary_skills and len(summary_skills) > 0:
            # 检查汇总文件是否与实际目录同步
            if is_skills_synchronized(summary_skills, skill_dirs):
                return summary_skills
        
        # 步骤3: 汇总文件不存在、为空或不同步，执行目录扫描
        scanned_skills = await scan_skills_directory(repo_path, 'project')

        # 步骤4: 扫描后生成汇总文件
        if len(scanned_skills) > 0:
            try:
                await write_skill_summary(repo_path, scanned_skills, 'project')
            except Exception:
                pass

        return scanned_skills
    except Exception:
        return []


async def get_user_skills() -> List[SkillMetadata]:
    """
    获取用户级别技能

    Returns:
        用户级技能元数据列表
    """
    try:
        user_skills_dir = get_user_skills_dir()

        # 步骤1: 检查汇总文件是否存在
        summary_skills = await read_skill_summary(user_skills_dir, 'user')

        # 步骤2: 获取物理目录下的技能列表，用于同步检查
        skill_dirs: List[str] = []
        if dir_exists(user_skills_dir):
            entries = read_dir(user_skills_dir)
            skill_dirs = [d for d in entries if d != 'SKILL.md' and not d.startswith('.')]

        if summary_skills and len(summary_skills) > 0:
            # 检查汇总文件是否与实际目录同步
            if is_skills_synchronized(summary_skills, skill_dirs):
                return summary_skills

        # 步骤3: 汇总文件不存在、为空或不同步，执行目录扫描
        scanned_skills = await scan_skills_directory(user_skills_dir, 'user')

        # 步骤4: 扫描后生成汇总文件
        if len(scanned_skills) > 0:
            try:
                await write_skill_summary(user_skills_dir, scanned_skills, 'user')
            except Exception:
                pass

        return scanned_skills
    except Exception:
        return []


async def get_available_skills(repo_path: str) -> List[SkillMetadata]:
    """
    获取指定目录下的所有skills
    采用索引机制优化性能：优先从汇总文件读取，避免全目录扫描
    支持项目级别（.joycode/skills/）和用户级别（~/.joycode/skills/）两种技能来源
    项目级技能优先覆盖同名用户级技能

    Args:
        repo_path: 项目根目录路径

    Returns:
        技能元数据列表（合并项目级和用户级）
    """
    try:
        # 并行获取项目级和用户级技能
        project_skills = await get_project_skills(repo_path)
        user_skills = await get_user_skills()

        # 合并技能列表，项目级优先
        return merge_skills(project_skills, user_skills)
    except Exception:
        return []


async def match_and_execute_skill(
    requirements: str,
    repo_path: str,
    file_path: Optional[str] = None
) -> str:
    """
    匹配并执行skills
    
    Args:
        requirements: 用户需求内容
        repo_path: 项目根目录路径
        file_path: 关联文件路径（可选）
        
    Returns:
        执行指导提示
    """
    try:
        # 获取所有skills，目录为repoPath/.joycode/skills/
        # 优先从汇总文件快速获取技能索引
        available_skills = await get_available_skills(repo_path)
        
        if len(available_skills) == 0:
            return f"""未找到可用的skills，请确保项目目录下存在 .joycode/skills 目录和相应的skills文件。

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir={repo_path}, tool_type=skills_expert"""
        
        # 构建详细的skills列表，包含目录名和路径信息（用于大模型匹配和路径构建）
        skills_list_with_dir = '\n'.join([
            f"- {skill.name} (目录: {skill.dir_name}, 来源: {skill.location}): {skill.description} (path: {skill.path})"
            for skill in available_skills
        ])
        
        # 获取用户级别 skills 目录路径
        user_skills_dir = get_user_skills_dir()

        # 生成指导大模型匹配和执行skills的提示
        skill_execution_prompt = f"""# Skill 匹配与执行指导

## 索引机制说明

本系统采用汇总文件索引机制，实现技能的快速定位和高效执行：

1. **索引查找**：下面的技能列表来自系统索引，包含所有可用技能的 name、description 和 path
2. **智能匹配**：根据用户需求，从索引中快速识别最匹配的技能
3. **快速定位**：使用索引中提供的 `path` 直接读取技能详情
4. **高效执行**：避免全目录扫描，实现"索引查找 → 快速定位 → 高效执行"的流程

**重要提示**：
- 技能名称（name）可能包含中文，但构建路径时必须使用目录名（dir_name）！
- 技能分为项目级（project）和用户级（user）两种来源：
  - 项目级路径：`{repo_path}/.joycode/skills/{{dir_name}}/SKILL.md`
  - 用户级路径：`{user_skills_dir}/{{dir_name}}/SKILL.md`
- 同名技能中，项目级优先于用户级

## 用户需求
{requirements}

## 可用skills索引
{skills_list_with_dir}

## 执行步骤

### 步骤 1: skills匹配
请根据上述用户需求和可用skills索引，选择最匹配的skills。考虑：
- skills描述与用户需求的关联度
- skills名称与任务类型的匹配度
- 选择最能满足用户需求的skills

### 步骤 2: 读取详情

选择最佳匹配skills后，**使用索引中提供的 path 字段**直接读取其详细内容：

```python
from pathlib import Path
matched_skill = [选择的skills对象]
# ⚠️ 重要：直接使用索引中的 path 字段读取技能详情
# path 已经根据技能来源（project/user）自动构建好了
skill_path = matched_skill.path
skill_content = await read_file(skill_path)
```

**注意**：
- path 字段已包含完整路径，无需手动拼接
- 项目级技能路径示例：`{repo_path}/.joycode/skills/java-controller-generator/SKILL.md`
- 用户级技能路径示例：`{user_skills_dir}/common-utils/SKILL.md`

### 步骤 3: 执行skills

根据skillContent中的指导，执行相应的操作来实现用户需求。

### 步骤 4: 记录日志

在完成技能执行后，调用「log_report」工具记录日志，**并传入本次匹配到的技能名称**：

- **[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报
- 参数：work_dir={repo_path}, tool_type=skills_expert, **skill_name=matched_skill.name**
- 说明：skill_name 使用匹配到的技能显示名（matched_skill.name），用于上报实际执行的技能

### 步骤 5: 检查

- 从模版复制进来的无用代码要删除掉
- 确保代码能正确运行
- 确保已正确调用 log_report，且包含实际匹配的技能名称（skill_name）

## 当前上下文

- 项目路径: {repo_path}
- 用户级skills目录: {user_skills_dir}
- 关联文件: {file_path or '无'}
- 可用skills数量: {len(available_skills)}
- 索引机制: 已启用，支持双目录查找和快速定位

请严格按照以上步骤执行，利用索引机制先匹配 skills，使用 dir_name 通过标准路径读取详情，执行 skills，**完成后调用 log_report 并传入 skill_name=matched_skill.name**。
"""
        
        return skill_execution_prompt
        
    except Exception as e:
        error_message = str(e)
        return f"""执行skills时发生错误: {error_message}

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir={repo_path}, tool_type=skills_expert"""


async def handle_skills_expert(
    requirements: Optional[str] = None,
    requirements_file: Optional[str] = None,
    repo_path: str = None
) -> str:
    """
    智能 skills 专家，根据需求描述或需求文件，自动匹配并执行相应的skills来完成任务
    
    Args:
        requirements: 用户需求描述
        requirements_file: 需求文件相对项目根目录的路径
        repo_path: Git 仓库目录（绝对路径）
        
    Returns:
        执行指导提示
    """
    try:
        # 参数验证
        if not requirements and not requirements_file:
            return f"""必须提供 requirements 或 requirements_file 其中之一

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir={repo_path or 'unknown'}, tool_type=skills_expert"""
        
        if not repo_path:
            return """必须提供 repo_path 参数

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir=unknown, tool_type=skills_expert"""
        
        # 获取需求内容
        requirements_content = requirements or ""
        
        if requirements_file:
            file_path = Path(repo_path) / requirements_file
            if file_exists(file_path):
                requirements_content = await read_file(file_path)
            else:
                return f"""需求文件不存在: {requirements_file}

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir={repo_path}, tool_type=skills_expert"""
        
        if not requirements_content.strip():
            return f"""需求内容为空，请提供有效的需求描述或需求文件

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir={repo_path}, tool_type=skills_expert"""
        
        # 匹配并执行
        result = await match_and_execute_skill(
            requirements_content,
            repo_path,
            str(requirements_file) if requirements_file else None
        )
        
        return result
        
    except Exception as e:
        error_message = str(e)
        return f"""Skills 工具执行失败: {error_message}

**[AI Agent Action]**请呼叫「log_report」工具进行本次工具使用记录上报。
参数：work_dir={repo_path or 'unknown'}, tool_type=skills_expert"""
