# =============================================================================
# WOOO TECH - Momo Pro System
# CI/CD Dashboard Routes
# =============================================================================

from flask import Blueprint, jsonify, render_template, request
import requests
import subprocess
from datetime import datetime
import json
import logging
import os
import re

cicd_bp = Blueprint('cicd', __name__)
cicd_log = logging.getLogger('cicd_routes')

# =============================================================================
# 錯誤分類與修復建議
# =============================================================================

ERROR_PATTERNS = {
    'registry_connection': {
        'pattern': r'(connection refused|registry.*unreachable|pull.*failed)',
        'message': 'Registry 連線失敗',
        'severity': 'critical',
        'fix_suggestion': '檢查 Registry 服務狀態，可嘗試自動修復',
        'auto_fixable': True,
        'fix_action': 'restart_registry'
    },
    'deploy_timeout': {
        'pattern': r'(timed out|timeout|deadline exceeded)',
        'message': '部署操作超時',
        'severity': 'warning',
        'fix_suggestion': '網路可能不穩定，請稍後重試或檢查 Gitea/CD runner 與 188 Docker Compose 狀態',
        'auto_fixable': False
    },
    'runtime_crash': {
        'pattern': r'(CrashLoopBackOff|OOMKilled|Error|ImagePullBackOff)',
        'message': '容器或舊叢集狀態異常',
        'severity': 'critical',
        'fix_suggestion': 'EwoooC 現行 runtime 是 188 Docker Compose；請依 DevOps 手冊檢查容器與 /health',
        'auto_fixable': False
    },
    'test_failed': {
        'pattern': r'(pytest.*failed|test.*error|AssertionError)',
        'message': '測試失敗',
        'severity': 'warning',
        'fix_suggestion': '檢查測試程式碼或修復功能錯誤',
        'auto_fixable': False
    },
    'build_failed': {
        'pattern': r'(docker.*build.*failed|pip.*install.*error|ModuleNotFoundError)',
        'message': '建置失敗',
        'severity': 'critical',
        'fix_suggestion': '檢查 Dockerfile 或 requirements.txt',
        'auto_fixable': False
    },
    'ssh_failed': {
        'pattern': r'(ssh.*connection.*refused|Permission denied|Host key verification)',
        'message': 'SSH 連線失敗',
        'severity': 'critical',
        'fix_suggestion': '檢查 SSH 金鑰配置或網路連線',
        'auto_fixable': False
    }
}

def analyze_error(text):
    """分析錯誤文字並返回結構化錯誤信息"""
    if not text:
        return None

    for error_type, config in ERROR_PATTERNS.items():
        if re.search(config['pattern'], text, re.IGNORECASE):
            return {
                'type': error_type,
                'message': config['message'],
                'severity': config['severity'],
                'fix_suggestion': config['fix_suggestion'],
                'auto_fixable': config['auto_fixable'],
                'fix_action': config.get('fix_action')
            }
    return None

# GitLab 配置
GITLAB_URL = os.environ.get('GITLAB_URL', 'http://192.168.0.110:8929')
GITLAB_TOKEN = os.environ.get('GITLAB_TOKEN', '')
GITLAB_PROJECT_ID = os.environ.get('GITLAB_PROJECT_ID', '1')
GITLAB_ENABLED = (
    os.environ.get('GITLAB_ENABLED', 'false').lower() in {'1', 'true', 'yes', 'on'}
    and bool(GITLAB_TOKEN)
)

if not GITLAB_ENABLED:
    cicd_log.info(
        '[CI/CD] GitLab legacy API disabled; set GITLAB_ENABLED=true and GITLAB_TOKEN to query legacy pipelines.'
    )

# 環境配置
ENVIRONMENTS = {
    'uat': {
        'name': 'UAT',
        'label': '測試環境',
        'color': '#3498db',
        'icon': '🟦',
        'url': 'https://mo.wooo.work',
        'health_endpoint': 'https://mo.wooo.work/health',
        'runtime_host': '192.168.0.188'
    },
    'prod': {
        'name': 'PROD',
        'label': '正式環境',
        'color': '#e74c3c',
        'icon': '🟥',
        'url': 'https://momo.wooo.work',
        'health_endpoint': 'https://momo.wooo.work/health',
        'runtime_host': '192.168.0.188'
    }
}

# =============================================================================
# Dashboard 頁面
# =============================================================================

@cicd_bp.route('/cicd')
def cicd_dashboard():
    """CI/CD Dashboard 主頁面"""
    return render_template('cicd_dashboard.html', active_page='cicd')

# =============================================================================
# API 端點
# =============================================================================

@cicd_bp.route('/api/cicd/status')
def get_cicd_status():
    """取得完整的 CI/CD 狀態"""
    try:
        # 收集所有狀態
        pipelines = get_recent_pipelines(limit=10)
        latest_pipeline = pipelines[0] if pipelines else None
        environments = get_all_environments_status()

        # 取得最新 Pipeline 的詳細 Job 信息
        latest_jobs = []
        failed_jobs = []
        if latest_pipeline:
            latest_jobs = get_pipeline_jobs(latest_pipeline['id'])
            failed_jobs = [j for j in latest_jobs if j.get('status') == 'failed']

            # 如果最新 Pipeline 失敗且有未通知的失敗 Job，發送告警
            if latest_pipeline.get('status') == 'failed' and failed_jobs:
                # 使用緩存避免重複通知
                cache_key = f"pipeline_alert_{latest_pipeline['id']}"
                if not hasattr(get_cicd_status, '_alert_cache'):
                    get_cicd_status._alert_cache = set()

                if cache_key not in get_cicd_status._alert_cache:
                    send_pipeline_failure_alert(latest_pipeline, failed_jobs)
                    get_cicd_status._alert_cache.add(cache_key)
                    # 限制緩存大小
                    if len(get_cicd_status._alert_cache) > 100:
                        get_cicd_status._alert_cache = set(list(get_cicd_status._alert_cache)[-50:])

        # 生成問題摘要
        issues = []

        # 檢查環境問題
        for env_id, env_status in environments.items():
            if not env_status.get('healthy'):
                issues.append({
                    'type': 'environment',
                    'environment': env_id,
                    'message': f"{env_status.get('name')} 環境異常",
                    'error': env_status.get('error'),
                    'severity': 'critical',
                    'auto_fixable': True,
                    'fix_action': 'diagnose'
                })

            # 舊 runtime 資訊若由歷史資料帶入，只允許診斷，不觸發重啟副作用。
            for pod in env_status.get('pods', []):
                if not pod.get('healthy'):
                    issues.append({
                        'type': 'runtime',
                        'environment': env_id,
                        'message': f"服務 {pod.get('name')} 不健康",
                        'status': pod.get('status'),
                        'restarts': pod.get('restarts'),
                        'severity': 'warning' if pod.get('restarts', 0) < 5 else 'critical',
                        'auto_fixable': True,
                        'fix_action': 'diagnose'
                    })

        # 檢查 Pipeline 問題
        for job in failed_jobs:
            error_info = job.get('error_info') or {}
            issues.append({
                'type': 'job',
                'job_name': job.get('name'),
                'stage': job.get('stage'),
                'message': error_info.get('message', job.get('failure_reason', 'Job 失敗')),
                'error_log': job.get('error_log', '')[:500],  # 限制長度
                'severity': error_info.get('severity', 'warning'),
                'auto_fixable': error_info.get('auto_fixable', False),
                'fix_action': error_info.get('fix_action'),
                'fix_suggestion': error_info.get('fix_suggestion')
            })

        return jsonify({
            'success': True,
            'timestamp': datetime.now().isoformat(),
            'latest_pipeline': latest_pipeline,
            'latest_jobs': latest_jobs,
            'pipelines': pipelines,
            'environments': environments,
            'issues': issues,
            'summary': {
                'total_pipelines_today': sum(1 for p in pipelines if is_today(p.get('created_at', ''))),
                'success_rate': calculate_success_rate(pipelines),
                'uat_healthy': environments.get('uat', {}).get('healthy', False),
                'prod_healthy': environments.get('prod', {}).get('healthy', False),
                'issues_count': len(issues),
                'critical_issues': sum(1 for i in issues if i.get('severity') == 'critical')
            }
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e),
            'timestamp': datetime.now().isoformat()
        }), 500


@cicd_bp.route('/api/cicd/pipelines')
def get_pipelines():
    """取得最近的 Pipeline 列表"""
    limit = request.args.get('limit', 20, type=int)
    pipelines = get_recent_pipelines(limit=limit)
    return jsonify({
        'success': True,
        'pipelines': pipelines,
        'count': len(pipelines)
    })


@cicd_bp.route('/api/cicd/pipeline/<int:pipeline_id>')
def get_pipeline_detail(pipeline_id):
    """取得單一 Pipeline 詳細資訊"""
    try:
        pipeline = get_pipeline_info(pipeline_id)
        jobs = get_pipeline_jobs(pipeline_id)

        return jsonify({
            'success': True,
            'pipeline': pipeline,
            'jobs': jobs,
            'stages': extract_stages(jobs)
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500


@cicd_bp.route('/api/cicd/environments')
def get_environments():
    """取得所有環境狀態"""
    environments = get_all_environments_status()
    return jsonify({
        'success': True,
        'environments': environments,
        'timestamp': datetime.now().isoformat()
    })


@cicd_bp.route('/api/cicd/environment/<env_id>')
def get_environment_detail(env_id):
    """取得單一環境詳細狀態"""
    if env_id not in ENVIRONMENTS:
        return jsonify({'success': False, 'error': 'Unknown environment'}), 404

    env_status = get_environment_status(env_id)
    return jsonify({
        'success': True,
        'environment': env_status
    })


@cicd_bp.route('/api/cicd/deploy', methods=['POST'])
def trigger_deploy():
    """手動觸發部署"""
    data = request.get_json() or {}
    env = data.get('environment', 'uat')

    if env not in ENVIRONMENTS:
        return jsonify({'success': False, 'error': 'Unknown environment'}), 400

    # 觸發 GitLab Pipeline
    try:
        result = trigger_gitlab_pipeline(ref='main', variables={
            'DEPLOY_ENV': env
        })
        return jsonify({
            'success': True,
            'message': f'已觸發 {ENVIRONMENTS[env]["name"]} 部署',
            'pipeline': result
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500


@cicd_bp.route('/api/cicd/rollback', methods=['POST'])
def trigger_rollback():
    """觸發回滾"""
    data = request.get_json() or {}
    env = data.get('environment', 'uat')

    if env not in ENVIRONMENTS:
        return jsonify({'success': False, 'error': 'Unknown environment'}), 400

    return jsonify({
        'success': False,
        'error': (
            '舊叢集回滾已停用。EwoooC 現行 runtime 是 188 Docker Compose；'
            '請依 Gitea CD / docs/guides/deployment_sop.md 執行人工回滾。'
        )
    }), 410


# =============================================================================
# 自動修復 API
# =============================================================================

# 只允許這幾種 fix_action，任何不在清單的請求直接 400。
# 舊叢集自動重啟已依 ADR-008/011 停用，避免誤打已撤除 runtime。
ALLOWED_FIX_ACTIONS = frozenset({'restart_registry', 'diagnose', 'full_repair'})


@cicd_bp.route('/api/cicd/auto-fix', methods=['POST'])
def trigger_auto_fix():
    """自動診斷並修復問題（allowlist 嚴格過濾）"""
    data = request.get_json() or {}
    fix_action = data.get('action')
    env = data.get('environment', 'uat')

    if env not in ENVIRONMENTS:
        return jsonify({'success': False, 'error': '未知環境'}), 400

    # Security: 嚴格 allowlist，防止任意 action 注入
    if fix_action not in ALLOWED_FIX_ACTIONS:
        return jsonify({'success': False, 'error': f'不允許的修復動作: {fix_action}'}), 400

    results = []
    try:
        if fix_action == 'restart_registry':
            result = fix_registry()
            results.append(result)
        elif fix_action == 'diagnose':
            result = run_diagnosis(env)
            results.append(result)
        elif fix_action == 'full_repair':
            # 完整修復保留 registry best-effort；runtime 只診斷，不做舊叢集重啟。
            results.append(fix_registry())
            results.append(run_diagnosis(env))

        # 發送 Telegram 通知
        send_fix_notification(env, fix_action, results)

        return jsonify({
            'success': True,
            'message': f'已執行修復動作: {fix_action}',
            'results': results
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500


@cicd_bp.route('/api/cicd/diagnose', methods=['POST'])
def diagnose_environment():
    """診斷環境問題"""
    data = request.get_json() or {}
    env = data.get('environment', 'uat')

    if env not in ENVIRONMENTS:
        return jsonify({'success': False, 'error': '未知環境'}), 400

    try:
        diagnosis = run_diagnosis(env)
        return jsonify({
            'success': True,
            'diagnosis': diagnosis
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500


def fix_registry():
    """修復 Registry 服務"""
    try:
        result = subprocess.run(
            ['ssh', '-o', 'StrictHostKeyChecking=no', '-o', 'ConnectTimeout=5',
             'wooo@192.168.0.110',
             'cd /home/wooo/devops/registry && docker compose restart'],
            capture_output=True, text=True, timeout=60
        )
        return {
            'action': 'restart_registry',
            'success': result.returncode == 0,
            'output': result.stdout,
            'error': result.stderr if result.returncode != 0 else None
        }
    except Exception as e:
        return {'action': 'restart_registry', 'success': False, 'error': str(e)}


def run_diagnosis(env):
    """執行環境診斷"""
    diagnosis = {
        'action': 'diagnose',
        'environment': env,
        'timestamp': datetime.now().isoformat(),
        'checks': []
    }

    try:
        # 檢查健康端點
        env_config = ENVIRONMENTS.get(env, {})
        try:
            # 內部健康檢查，可能使用自簽憑證 nosec B501
            response = requests.get(env_config.get('health_endpoint'), timeout=10, verify=False)  # nosec B501
            diagnosis['checks'].append({
                'name': '健康端點',
                'status': 'ok' if response.status_code == 200 else 'failed',
                'response_time': response.elapsed.total_seconds() * 1000,
                'status_code': response.status_code
            })
        except Exception as e:
            diagnosis['checks'].append({
                'name': '健康端點',
                'status': 'failed',
                'error': str(e)
            })

        # EwoooC 已撤除舊叢集 runtime，這裡只保留現行 Docker Compose 狀態說明。
        diagnosis['checks'].append({
            'name': 'Runtime 狀態',
            'status': 'ok',
            'runtime': 'Docker Compose on 192.168.0.188',
            'details': '舊叢集探測已停用；容器狀態請依 DevOps 手冊在 188 查 docker compose / /health。'
        })

        # 檢查 Registry (僅 UAT)
        if env == 'uat':
            try:
                # Registry 健康檢查 nosec B501
                reg_response = requests.get('https://registry.wooo.work/v2/', timeout=10, verify=False)  # nosec B501
                diagnosis['checks'].append({
                    'name': 'Registry',
                    'status': 'ok' if reg_response.status_code in [200, 401] else 'failed',
                    'status_code': reg_response.status_code
                })
            except Exception as e:
                diagnosis['checks'].append({
                    'name': 'Registry',
                    'status': 'failed',
                    'error': str(e)
                })

        # 生成總結
        failed_checks = [c for c in diagnosis['checks'] if c['status'] == 'failed']
        warning_checks = [c for c in diagnosis['checks'] if c['status'] == 'warning']

        diagnosis['summary'] = {
            'overall_status': 'critical' if failed_checks else ('warning' if warning_checks else 'healthy'),
            'failed_count': len(failed_checks),
            'warning_count': len(warning_checks),
            'recommendations': []
        }

        # 生成修復建議
        for check in failed_checks:
            if check['name'] == '健康端點':
                diagnosis['summary']['recommendations'].append({
                    'action': 'diagnose',
                    'description': '先診斷健康端點與 188 Docker Compose 狀態，避免自動重啟資料庫或舊叢集'
                })
            elif check['name'] == 'Registry':
                diagnosis['summary']['recommendations'].append({
                    'action': 'restart_registry',
                    'description': '建議重啟 Registry 服務'
                })

    except Exception as e:
        diagnosis['error'] = str(e)

    return diagnosis


# =============================================================================
# Telegram 告警
# =============================================================================

TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '')
_chat_ids_raw = os.environ.get('TELEGRAM_CHAT_IDS', '[]')
try:
    _chat_ids_list = json.loads(_chat_ids_raw)
    TELEGRAM_CHAT_ID = str(_chat_ids_list[0]) if _chat_ids_list else os.environ.get('TELEGRAM_CHAT_ID', '')
except Exception:
    TELEGRAM_CHAT_ID = os.environ.get('TELEGRAM_CHAT_ID', '')

if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
    import logging
    logging.getLogger('cicd_routes').warning(
        '[SECURITY] TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set. '
        'Telegram notifications will silently fail. Set these environment variables.'
    )


def send_telegram_message(message):
    """
    發送 Telegram 訊息（CI/CD pipeline 失敗等告警）

    ADR-019 Phase 5: 改走 EventRouter 統一入口。
    舊版 parse_mode=Markdown 改由 EventRouter 模板統一 HTML（CI/CD 訊息以 *bold*/
    backtick code 為主，HTML <b>/<code> 包裝由 templates 處理；既有訊息會直接
    當 plain text 顯示，不再渲染 Markdown 控制字元，可讀性反而提升）。
    """
    try:
        from services.event_router import dispatch_sync
        admin_ids = [TELEGRAM_CHAT_ID] if TELEGRAM_CHAT_ID else None
        result = dispatch_sync(event={
            "event_type": "cicd_pipeline_alert",
            "severity":   "alert",
            "source":     "CICD.Routes",
            "title":      "CI/CD 告警",
            "summary":    message[:400],
            "status":     "pipeline_event",
            "payload":    {"raw_message": message},
        }, admin_chat_ids=admin_ids)
        return bool(result.get("delivered"))
    except Exception as e:
        print(f"Telegram send error: {e}")
        return False


def send_pipeline_failure_alert(pipeline, failed_jobs):
    """Pipeline 失敗時發送告警"""
    job_details = '\n'.join([
        f"  • {j['name']}: {j.get('failure_reason', '未知原因')}"
        for j in failed_jobs
    ])

    message = f"""🚨 *CI/CD Pipeline 失敗*

📌 *Pipeline:* #{pipeline.get('id')}
🌿 *分支:* `{pipeline.get('ref')}`
📝 *Commit:* `{pipeline.get('sha', '')[:8]}`

❌ *失敗 Jobs:*
{job_details}

🔗 [查看詳情]({pipeline.get('web_url')})
"""
    return send_telegram_message(message)


def send_fix_notification(env, action, results):
    """發送修復通知"""
    env_icon = '🟦' if env == 'uat' else '🟥'
    env_name = ENVIRONMENTS.get(env, {}).get('name', env)

    success_count = sum(1 for r in results if r.get('success'))
    total_count = len(results)

    status_emoji = '✅' if success_count == total_count else '⚠️'

    result_details = '\n'.join([
        f"  • {r.get('action')}: {'✅ 成功' if r.get('success') else '❌ 失敗'}"
        for r in results
    ])

    message = f"""{status_emoji} *CI/CD 自動修復執行完成*

{env_icon} *環境:* {env_name}
🔧 *動作:* {action}
📊 *結果:* {success_count}/{total_count} 成功

{result_details}

⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
    return send_telegram_message(message)


# =============================================================================
# 輔助函數 - GitLab API
# =============================================================================

def gitlab_api(endpoint, method='GET', data=None):
    """呼叫 GitLab API（支援 SSH 備用方案）"""
    if not GITLAB_ENABLED:
        return None

    url = f"{GITLAB_URL}/api/v4{endpoint}"
    headers = {'PRIVATE-TOKEN': GITLAB_TOKEN}

    try:
        if method == 'GET':
            response = requests.get(url, headers=headers, timeout=10)
        elif method == 'POST':
            response = requests.post(url, headers=headers, json=data, timeout=10)

        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        cicd_log.warning("[CI/CD] GitLab direct API unavailable: %s", e)
        # 備用方案：透過 SSH 在主機上執行 curl
        return gitlab_api_via_ssh(endpoint, method, data)


def gitlab_api_via_ssh(endpoint, method='GET', data=None):
    """
    透過 SSH 在主機上呼叫 GitLab API（當 app 無法直接連接時）。

    Security: curl 參數以 list 形式傳給 subprocess，避免 shell injection。
    endpoint 和 json_data 均作為獨立 argv 傳入，不經過 shell 解析。
    """
    if not GITLAB_ENABLED:
        return None

    try:
        # 使用本地 GitLab URL；endpoint 由 gitlab_api() 內部構造，不含外部輸入
        url = f"http://127.0.0.1:8929/api/v4{endpoint}"

        if method == 'GET':
            # list 形式：每個 curl 參數獨立，不走 shell
            remote_argv = [
                'curl', '-s',
                '-H', f'PRIVATE-TOKEN: {GITLAB_TOKEN}',
                url
            ]
        else:
            json_data = json.dumps(data) if data else '{}'
            remote_argv = [
                'curl', '-s', '-X', 'POST',
                '-H', f'PRIVATE-TOKEN: {GITLAB_TOKEN}',
                '-H', 'Content-Type: application/json',
                '-d', json_data,
                url
            ]

        # SSH 以 list 模式執行：remote_argv 整體作為單一 SSH command 字串
        # 由於 remote_argv 內的 GitLab token 和 url 均來自受控環境變數，
        # 不含使用者輸入，此處拼接是安全的。
        ssh_cmd = ['ssh',
                   '-o', 'StrictHostKeyChecking=no',
                   '-o', 'ConnectTimeout=5',
                   'wooo@192.168.0.110'] + remote_argv

        result = subprocess.run(
            ssh_cmd,
            capture_output=True, text=True, timeout=15
        )

        if result.returncode == 0 and result.stdout:
            return json.loads(result.stdout)
        else:
            cicd_log.warning("[CI/CD] GitLab SSH API unavailable: %s", result.stderr)
            return None
    except Exception as e:
        cicd_log.warning("[CI/CD] GitLab SSH fallback failed: %s", e)
        return None


def get_recent_pipelines(limit=20):
    """取得最近的 Pipeline"""
    result = gitlab_api(f"/projects/{GITLAB_PROJECT_ID}/pipelines?per_page={limit}")
    if not result:
        return []

    pipelines = []
    for p in result:
        pipelines.append({
            'id': p.get('id'),
            'status': p.get('status'),
            'ref': p.get('ref'),
            'sha': p.get('sha', '')[:8],
            'web_url': p.get('web_url'),
            'created_at': p.get('created_at'),
            'updated_at': p.get('updated_at'),
            'duration': calculate_duration(p.get('created_at'), p.get('updated_at')),
            'status_icon': get_status_icon(p.get('status')),
            'status_color': get_status_color(p.get('status'))
        })

    return pipelines


def get_pipeline_info(pipeline_id):
    """取得 Pipeline 詳細資訊"""
    return gitlab_api(f"/projects/{GITLAB_PROJECT_ID}/pipelines/{pipeline_id}")


def get_pipeline_jobs(pipeline_id):
    """取得 Pipeline 的所有 Jobs（包含錯誤詳情）"""
    result = gitlab_api(f"/projects/{GITLAB_PROJECT_ID}/pipelines/{pipeline_id}/jobs")
    if not result:
        return []

    jobs = []
    for j in result:
        job_data = {
            'id': j.get('id'),
            'name': j.get('name'),
            'stage': j.get('stage'),
            'status': j.get('status'),
            'duration': j.get('duration'),
            'web_url': j.get('web_url'),
            'started_at': j.get('started_at'),
            'finished_at': j.get('finished_at'),
            'status_icon': get_status_icon(j.get('status')),
            'status_color': get_status_color(j.get('status')),
            'failure_reason': j.get('failure_reason'),
            'error_info': None
        }

        # 如果 Job 失敗，嘗試取得錯誤日誌摘要
        if j.get('status') == 'failed':
            error_log = get_job_error_summary(j.get('id'))
            if error_log:
                job_data['error_log'] = error_log
                job_data['error_info'] = analyze_error(error_log)

        jobs.append(job_data)

    return sorted(jobs, key=lambda x: (x.get('stage', ''), x.get('name', '')))


def get_job_error_summary(job_id, max_lines=50):
    """從 GitLab 取得 Job 日誌並提取錯誤摘要"""
    try:
        # 取得 Job 日誌（GitLab API 返回純文字）
        url = f"{GITLAB_URL}/api/v4/projects/{GITLAB_PROJECT_ID}/jobs/{job_id}/trace"
        headers = {'PRIVATE-TOKEN': GITLAB_TOKEN}
        response = requests.get(url, headers=headers, timeout=10)

        if response.status_code == 200:
            log_text = response.text
            lines = log_text.split('\n')

            # 找出錯誤相關行
            error_lines = []
            for i, line in enumerate(lines):
                if any(keyword in line.lower() for keyword in ['error', 'failed', 'exception', 'traceback', 'fatal']):
                    # 取得上下文
                    start = max(0, i - 2)
                    end = min(len(lines), i + 3)
                    context = lines[start:end]
                    error_lines.extend(context)

            if error_lines:
                # 去重並限制長度
                unique_lines = list(dict.fromkeys(error_lines))[:max_lines]
                return '\n'.join(unique_lines)

            # 如果沒找到明確錯誤，返回最後幾行
            return '\n'.join(lines[-20:])

    except Exception as e:
        print(f"Error getting job log for {job_id}: {e}")

    return None


def extract_stages(jobs):
    """從 Jobs 提取 Stage 資訊"""
    stages = {}
    stage_order = ['test', 'build', 'deploy']

    for job in jobs:
        stage = job.get('stage', 'unknown')
        if stage not in stages:
            stages[stage] = {
                'name': stage,
                'status': 'pending',
                'jobs': [],
                'duration': 0
            }
        stages[stage]['jobs'].append(job)

        # 更新 stage 狀態
        if job.get('status') == 'failed':
            stages[stage]['status'] = 'failed'
        elif job.get('status') == 'running' and stages[stage]['status'] != 'failed':
            stages[stage]['status'] = 'running'
        elif job.get('status') == 'success' and stages[stage]['status'] == 'pending':
            stages[stage]['status'] = 'success'

        # 累加時間
        if job.get('duration'):
            stages[stage]['duration'] += job.get('duration', 0)

    # 按順序排列
    ordered_stages = []
    for stage_name in stage_order:
        if stage_name in stages:
            stages[stage_name]['status_icon'] = get_status_icon(stages[stage_name]['status'])
            stages[stage_name]['status_color'] = get_status_color(stages[stage_name]['status'])
            ordered_stages.append(stages[stage_name])

    return ordered_stages


def trigger_gitlab_pipeline(ref='main', variables=None):
    """觸發 GitLab Pipeline"""
    data = {'ref': ref}
    if variables:
        data['variables'] = [{'key': k, 'value': v} for k, v in variables.items()]

    return gitlab_api(f"/projects/{GITLAB_PROJECT_ID}/pipeline", method='POST', data=data)


# =============================================================================
# 輔助函數 - 環境狀態
# =============================================================================

def get_all_environments_status():
    """取得所有環境狀態"""
    environments = {}
    for env_id, env_config in ENVIRONMENTS.items():
        environments[env_id] = get_environment_status(env_id)
    return environments


def get_environment_status(env_id):
    """取得單一環境狀態"""
    env_config = ENVIRONMENTS.get(env_id, {})

    status = {
        'id': env_id,
        'name': env_config.get('name'),
        'label': env_config.get('label'),
        'color': env_config.get('color'),
        'icon': env_config.get('icon'),
        'url': env_config.get('url'),
        'healthy': False,
        'pods': [],
        'last_deploy': None,
        'version': None,
        'response_time': None
    }

    # 健康檢查（內部服務可能使用自簽憑證）
    try:
        start_time = datetime.now()
        response = requests.get(
            env_config.get('health_endpoint'),
            timeout=10,
            verify=False  # nosec B501 - 內部健康檢查
        )
        response_time = (datetime.now() - start_time).total_seconds() * 1000

        if response.status_code == 200:
            health_data = response.json()
            status['healthy'] = health_data.get('status') == 'healthy'
            status['version'] = health_data.get('version')
            status['response_time'] = round(response_time, 2)
            status['last_check'] = datetime.now().isoformat()
    except Exception as e:
        status['error'] = str(e)

    status['runtime_note'] = 'Docker Compose on 192.168.0.188; legacy cluster probes disabled.'

    return status


# =============================================================================
# 輔助函數 - 通用
# =============================================================================

def get_status_icon(status):
    """取得狀態圖示"""
    icons = {
        'success': '✅',
        'passed': '✅',
        'failed': '❌',
        'running': '🔄',
        'pending': '⏳',
        'canceled': '⛔',
        'skipped': '⏭️',
        'manual': '👆',
        'created': '🆕'
    }
    return icons.get(status, '❓')


def get_status_color(status):
    """取得狀態顏色"""
    colors = {
        'success': '#28a745',
        'passed': '#28a745',
        'failed': '#dc3545',
        'running': '#007bff',
        'pending': '#ffc107',
        'canceled': '#6c757d',
        'skipped': '#6c757d',
        'manual': '#17a2b8',
        'created': '#6c757d'
    }
    return colors.get(status, '#6c757d')


def calculate_duration(start, end):
    """計算持續時間"""
    if not start or not end:
        return None
    try:
        start_dt = datetime.fromisoformat(start.replace('Z', '+00:00'))
        end_dt = datetime.fromisoformat(end.replace('Z', '+00:00'))
        duration = (end_dt - start_dt).total_seconds()

        if duration < 60:
            return f"{int(duration)}秒"
        elif duration < 3600:
            return f"{int(duration // 60)}分{int(duration % 60)}秒"
        else:
            return f"{int(duration // 3600)}時{int((duration % 3600) // 60)}分"
    except:
        return None


def calculate_age(timestamp):
    """計算年齡"""
    if not timestamp:
        return "未知"
    try:
        created = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
        now = datetime.now(created.tzinfo)
        diff = now - created

        if diff.days > 0:
            return f"{diff.days}天"
        elif diff.seconds >= 3600:
            return f"{diff.seconds // 3600}時"
        elif diff.seconds >= 60:
            return f"{diff.seconds // 60}分"
        else:
            return f"{diff.seconds}秒"
    except:
        return "未知"


def is_today(timestamp):
    """判斷是否為今天"""
    if not timestamp:
        return False
    try:
        dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
        return dt.date() == datetime.now().date()
    except:
        return False


def calculate_success_rate(pipelines):
    """計算成功率"""
    if not pipelines:
        return 0

    success_count = sum(1 for p in pipelines if p.get('status') in ['success', 'passed'])
    return round(success_count / len(pipelines) * 100, 1)
