"""
Jingrow 应用安装器
"""

import os
import json
import shutil
import tempfile
import zipfile
import tarfile
import uuid
from pathlib import Path
from typing import Dict, List, Any
from datetime import datetime
from functools import wraps
import logging

from jingrow.config import Config
from jingrow.utils.jingrow_api import get_jingrow_api_headers
from jingrow.utils.jingrow_api import get_record_id, create_record
from jingrow.utils.app_manager import update_apps_txt
from jingrow.utils.path import get_root_path, get_apps_path

logger = logging.getLogger(__name__)


def handle_errors(func):
    """统一错误处理装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            return {'success': False, 'error': str(e)}
    return wrapper


def get_app_directories():
    """获取应用目录路径"""
    apps_dir = get_apps_path()
    apps_dir.mkdir(parents=True, exist_ok=True)
    
    return apps_dir, apps_dir


@handle_errors
def extract_package(zip_path: str) -> Dict[str, Any]:
    """解压安装包 - 支持 ZIP 和 TAR.GZ"""
    # 获取项目根目录
    tmp_dir = get_root_path() / "tmp"
    tmp_dir.mkdir(parents=True, exist_ok=True)
    
    # 创建唯一临时目录
    temp_dir_name = f"app_install_{uuid.uuid4().hex[:8]}"
    temp_dir = tmp_dir / temp_dir_name
    temp_dir.mkdir(parents=True, exist_ok=True)
    
    # 判断文件类型
    if zip_path.endswith('.tar.gz') or zip_path.endswith('.tgz') or zip_path.endswith('.gz'):
        with tarfile.open(zip_path, 'r:gz') as tar_ref:
            tar_ref.extractall(temp_dir)
    elif zip_path.endswith('.zip'):
        with zipfile.ZipFile(zip_path, 'r') as zip_ref:
            zip_ref.extractall(temp_dir)
    else:
        return {'success': False, 'error': '不支持的文件格式'}
    
    return {'success': True, 'temp_dir': str(temp_dir)}


@handle_errors
def analyze_package(temp_dir: str) -> Dict[str, Any]:
    """分析安装包结构"""
    # 查找根目录
    if not os.path.exists(temp_dir):
        return {'success': False, 'error': f'目录不存在: {temp_dir}'}
    
    root_items = os.listdir(temp_dir)
    
    if not root_items:
        return {'success': False, 'error': '目录为空'}
    
    # 对于解压后的目录，判断应用层级
    # 统计非系统目录的目录数量
    non_git_dirs = [item for item in root_items if os.path.isdir(os.path.join(temp_dir, item)) and item not in ['.git', '__pycache__']]
    
    root_dir = temp_dir
    
    # 如果只有一个目录，需要进入（解压后的标准结构）
    if len(non_git_dirs) == 1:
        root_dir = os.path.join(temp_dir, non_git_dirs[0])
    # 如果有多个目录（demo/, frontend/ 等），说明已经是应用根目录，直接使用
    
    package_info = {
        'app_name': os.path.basename(root_dir),
        'version': '1.0.0',
        'description': '',
        'has_backend': False,
        'has_frontend': False,
        'backend_files': [],
        'frontend_files': [],
        'root_dir': root_dir  # 保存根目录路径
    }
    
    # 检查配置文件
    if os.path.exists(os.path.join(root_dir, 'hooks.py')):
        package_info['has_backend'] = True
    
    if os.path.exists(os.path.join(root_dir, 'package.json')):
        package_info['has_frontend'] = True
        try:
            with open(os.path.join(root_dir, 'package.json'), 'r') as f:
                pkg_data = json.load(f)
                package_info.update({
                    'app_name': pkg_data.get('name', package_info['app_name']),
                    'version': pkg_data.get('version', package_info['version']),
                    'description': pkg_data.get('description', package_info['description'])
                })
        except:
            pass
    
    # 检查是否有 hooks.py（判断是否为独立应用）
    hooks_path = os.path.join(root_dir, 'hooks.py')
    if not os.path.exists(hooks_path):
        # 在子目录中查找
        for root, dirs, files in os.walk(root_dir):
            if 'hooks.py' in files:
                hooks_path = os.path.join(root, 'hooks.py')
                break
    
    # 从 hooks.py 提取 app_name
    if os.path.exists(hooks_path):
        try:
            with open(hooks_path, 'r', encoding='utf-8') as f:
                content = f.read()
                import re
                name_match = re.search(r'app_name\s*=\s*["\']([^"\']+)["\']', content)
                if name_match:
                    package_info['app_name'] = name_match.group(1)
        except:
            pass
    
    package_info['has_hooks'] = os.path.exists(hooks_path)
    
    # 扫描文件
    for root, dirs, files in os.walk(root_dir):
        for file in files:
            rel_path = os.path.relpath(os.path.join(root, file), root_dir)
            
            if file.endswith(('.py', '.txt', '.cfg', '.json', '.md', '.LICENSE', '.yaml', '.yml')):
                package_info['backend_files'].append(rel_path)
                package_info['has_backend'] = True
            elif file.endswith(('.vue', '.ts', '.js', '.css')):
                package_info['frontend_files'].append(rel_path)
                package_info['has_frontend'] = True
    
    return {'success': True, 'data': package_info}


@handle_errors
def install_files(temp_dir: str, app_name: str, package_info: Dict[str, Any]) -> Dict[str, Any]:
    """安装文件到指定目录 - 完整复制整个包结构（业内标准做法）"""
    apps_dir, _ = get_app_directories()
    app_dir = apps_dir / app_name
    
    # 创建应用目录
    app_dir.mkdir(parents=True, exist_ok=True)
    
    # 获取根目录（analyze_package 已经找到正确的根目录）
    root_dir = package_info.get('root_dir', temp_dir)
    
    # 直接复制整个包结构（只跳过系统生成的文件）
    for item in os.listdir(root_dir):
        if item in ['__pycache__', '.DS_Store']:
            continue
        
        src = os.path.join(root_dir, item)
        dst = app_dir / item
        
        if os.path.isdir(src):
            if dst.exists():
                shutil.rmtree(dst)
            shutil.copytree(src, dst)
        else:
            shutil.copy2(src, dst)
    
    return {
        'success': True,
        'app_dir': str(app_dir)
    }


@handle_errors
def record_installation(app_name: str, package_info: Dict[str, Any], upload_path: str) -> Dict[str, Any]:
    """记录安装信息到数据库"""
    api_url = f"{Config.jingrow_server_url}/api/action/jingrow.core.pagetype.local_installed_apps.local_installed_apps.insert"
    
    from jingrow.utils.auth import get_jingrow_api_headers
    headers = get_jingrow_api_headers()
    
    import requests
    payload = {
        'app_name': app_name,
        'version': package_info.get('version', '1.0.0'),
        'description': package_info.get('description', ''),
        'installation_date': datetime.now().isoformat(),
        'upload_path': upload_path,
        'has_backend': package_info.get('has_backend', False),
        'has_frontend': package_info.get('has_frontend', False)
    }
    
    response = requests.post(api_url, json=payload, headers=headers, timeout=30)
    
    if response.status_code == 200:
        return {'success': True, 'message': '记录成功'}
    else:
        return {'success': False, 'error': f'记录失败: HTTP {response.status_code}'}


def cleanup_temp_dir(temp_dir: str):
    """清理临时目录"""
    try:
        if os.path.exists(temp_dir):
            shutil.rmtree(temp_dir)
    except Exception:
        pass


def is_app_installed(app_name: str) -> bool:
    """检查应用是否已安装"""
    apps_dir, _ = get_app_directories()
    app_dir = apps_dir / app_name
    return app_dir.exists()


def get_installed_apps() -> List[Dict[str, Any]]:
    """获取已安装的应用列表"""
    apps = []
    apps_dir, _ = get_app_directories()
    
    # 扫描apps目录
    if apps_dir.exists():
        for app_dir in apps_dir.iterdir():
            if app_dir.is_dir():
                app_info = {
                    'name': app_dir.name,
                    'type': 'unknown',
                    'path': str(app_dir),
                    'exists': True
                }
                
                # 检查是否有app_name和frontend
                backend_dir = app_dir / app_dir.name
                frontend_dir = app_dir / "frontend"
                
                if backend_dir.exists() and frontend_dir.exists():
                    app_info['type'] = 'both'
                elif backend_dir.exists():
                    app_info['type'] = app_dir.name
                elif frontend_dir.exists():
                    app_info['type'] = 'frontend'
                
                apps.append(app_info)
    
    return apps


def install_app(uploaded_file_path: str, app_name: str = None) -> Dict[str, Any]:
    """
    安装应用的主函数
    
    Args:
        uploaded_file_path: 上传文件的路径
        app_name: 应用名称（可选）
        
    Returns:
        Dict: 安装结果
    """
    try:
        
        # 验证文件
        if not os.path.exists(uploaded_file_path):
            return {'success': False, 'error': '文件不存在'}
        
        filename_lower = uploaded_file_path.lower()
        if not (filename_lower.endswith('.zip') or 
                filename_lower.endswith('.tar.gz') or 
                filename_lower.endswith('.tgz') or 
                filename_lower.endswith('.gz')):
            return {'success': False, 'error': '不支持的文件格式，支持ZIP、TAR.GZ、TGZ、GZ格式'}
        
        # 解压文件
        extract_result = extract_package(uploaded_file_path)
        if not extract_result.get('success'):
            return extract_result
        
        temp_dir = extract_result['temp_dir']
        
        # 分析包结构
        analyze_result = analyze_package(temp_dir)
        if not analyze_result.get('success'):
            cleanup_temp_dir(temp_dir)
            return analyze_result
        
        package_info = analyze_result['data']
        
        # 检查是否有 hooks.py
        has_hooks = package_info.get('has_hooks', False)
        
        if not has_hooks:
            # 作为扩展包安装到 jingrow 应用内部
            result = install_package(temp_dir, package_info)
            cleanup_temp_dir(temp_dir)
            return result
        
        # 独立应用安装流程
        # 确定应用名称
        if not app_name:
            app_name = package_info.get('app_name')
            if not app_name:
                cleanup_temp_dir(temp_dir)
                return {'success': False, 'error': '无法识别应用名称'}
        
        # 如果应用已安装，先删除旧版本（允许覆盖安装）
        if is_app_installed(app_name):
            apps_dir, _ = get_app_directories()
            app_dir = apps_dir / app_name
            try:
                if app_dir.exists():
                    shutil.rmtree(app_dir)
            except Exception as e:
                cleanup_temp_dir(temp_dir)
                return {'success': False, 'error': f'删除旧应用失败: {str(e)}'}
        
        # 安装文件（完整复制整个包结构）
        install_result = install_files(temp_dir, app_name, package_info)
        if not install_result.get('success'):
            cleanup_temp_dir(temp_dir)
            return install_result
        
        # 记录安装信息
        record_installation(app_name, package_info, uploaded_file_path)

        # 确保创建 Package 与 Module Def（custom）记录
        try:
            ensure_package_and_module(app_name)
        except Exception:
            # 忽略该步骤错误，不影响安装完成
            pass
        
        # 更新 apps.txt，确保路由生效
        update_apps_txt(app_name, add=True)
        
        # 清理临时文件
        cleanup_temp_dir(temp_dir)
        return {
            'success': True,
            'message': f'应用 {app_name} 安装成功',
            'app_name': app_name,
            'package_info': package_info,
            'app_dir': install_result.get('app_dir')
        }
        
    except Exception as e:
        return {'success': False, 'error': str(e)}


@handle_errors
def install_package(temp_dir: str, package_info: Dict[str, Any]) -> Dict[str, Any]:
    """将扩展包安装到 jingrow 应用内部并同步到数据库"""
    root_dir = package_info.get('root_dir', temp_dir)
    app_name = package_info.get('app_name')
    
    # 获取 jingrow 应用目录
    apps_dir, _ = get_app_directories()
    jingrow_backend_dir = apps_dir / "jingrow" / "jingrow"
    
    if not jingrow_backend_dir.exists():
        return {'success': False, 'error': '找不到 jingrow 应用目录'}
    
    # 直接使用 root_dir 作为源目录（扩展包只包含模块目录，不需要额外的目录层级）
    source_dir = root_dir
    
    # 先同步到数据库
    try:
        from jingrow.utils.jingrow_api import get_jingrow_api_headers
        from jingrow.config import Config
        import requests
        
        api_url = f"{Config.jingrow_server_url}/api/action/jingrow.ai.utils.jlocal.sync_app_files"
        response = requests.post(
            api_url,
            json={'app_name': app_name, 'app_path': source_dir, 'force': True},
            headers=get_jingrow_api_headers(),
            timeout=60
        )
        
        if response.status_code != 200:
            return {'success': False, 'error': f'同步到数据库失败: HTTP {response.status_code}'}
    except Exception as e:
        return {'success': False, 'error': f'同步到数据库失败: {str(e)}'}
    
    # 再复制文件到 jingrow（只复制模块目录，跳过配置和文档文件）
    for item in os.listdir(source_dir):
        if item in ['__pycache__', '.git', 'frontend'] or item.endswith('.json') or item in ['LICENSE.md', 'README.md', 'README']:
            continue
        
        src = os.path.join(source_dir, item)
        dst = jingrow_backend_dir / item
        
        if os.path.isdir(src):
            if dst.exists():
                shutil.rmtree(dst)
            shutil.copytree(src, dst)
        else:
            shutil.copy2(src, dst)
    
    return {'success': True, 'message': '扩展包已安装到 jingrow 应用', 'app_name': app_name}


def _safe_bool(value: Any, default: int = 1) -> int:
    try:
        if isinstance(value, bool):
            return 1 if value else 0
        if isinstance(value, (int, float)):
            return 1 if value else 0
        if isinstance(value, str):
            return 1 if value.strip().lower() in ("1", "true", "yes") else 0
    except Exception:
        pass
    return default


def _sanitize_package_name(name: str) -> str:
    """将任意app名称规范化为仅包含小写字母/数字/连字符的package_name。"""
    try:
        s = str(name or "").lower()
        s = s.replace(" ", "-").replace("_", "-")
        import re
        s = re.sub(r"[^a-z0-9-]", "", s)
        s = re.sub(r"-+", "-", s).strip("-")
        return s or "package"
    except Exception:
        return "package"


def ensure_package_and_module(app_name: str) -> None:
    """
    创建与 app_name 同名的 Package 记录和 Module Def 记录（custom 类型）。
    已存在时不会中断安装流程。
    """
    if not app_name:
        return

    # 创建 Package 记录（先检查是否已存在）
    safe_pkg_name = _sanitize_package_name(app_name)
    try:
        exists = get_record_id("Package", field="name", value=app_name)
        if not exists.get("success"):
            create_record("Package", {"name": app_name, "package_name": safe_pkg_name})
    except Exception:
        pass

    # 创建 Module Def 记录（custom 类型，先检查是否已存在）
    try:
        exists = get_record_id("Module Def", field="module_name", value=app_name)
        if not exists.get("success"):
            create_record("Module Def", {
                "module_name": app_name,
                "custom": 1,
                "package": app_name,
                "app_name": "jingrow"
            })
    except Exception:
        pass
