#!/usr/bin/env python3
"""
Analyze mirror sync failures using LLM.

Usage: analyze-fail.py <mirror_name>

Looks up logs in /data/logs/tunasync/{mirror_name}/, collects the last
successful log and all failed logs (last 200 lines if too large),
and sends them to an OpenAI-compatible endpoint for failure analysis.
"""

import os
import re
import sys
from datetime import datetime
from pathlib import Path

import requests

LOG_BASE = Path("/data/logs/tunasync")
MAX_LOG_LINES = 200
LLM_ENDPOINT = os.environ.get("LLM_ENDPOINT", "https://openapi.seu.edu.cn/v1/chat/completions")
LLM_MODEL = os.environ.get("LLM_MODEL", "qwen3.5-397b-a17b")
API_KEY = os.environ.get("LLM_API_KEY", "9c2fcf1e-afc3-4dc4-8b7e-636cdac31519")
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "1608542553:AAEbnI0p6CjF3JzM-oQIxAIpwohQS1gS7XY")
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "756817649")
TELEGRAM_API = "https://botapi.dawn.ee/bot{}/sendMessage".format(TELEGRAM_BOT_TOKEN)


def parse_log_filename(filename):
    """Parse log filename like rustup_2026-07-08_11_12.log.fail."""
    match = re.match(r"(.+)_(\d{4}-\d{2}-\d{2})_(\d{2})_(\d{2})\.log(\.fail)?$", filename)
    if not match:
        raise ValueError("Invalid log filename: {}".format(filename))
    
    name = match.group(1)
    date_str = "{}_{}_{}".format(match.group(2), match.group(3), match.group(4))
    is_fail = match.group(5) == ".fail"
    timestamp = datetime.strptime(date_str, "%Y-%m-%d_%H_%M")
    
    return name, timestamp, is_fail


def read_log_tail(filepath):
    """Read log file, returning last MAX_LOG_LINES if larger."""
    with open(filepath, "r", encoding="utf-8", errors="replace") as f:
        lines = f.readlines()
    
    if len(lines) > MAX_LOG_LINES:
        header = "... (truncated, showing last {} of {} lines)\n".format(MAX_LOG_LINES, len(lines))
        return header + "".join(lines[-MAX_LOG_LINES:])
    return "".join(lines)


def collect_logs(mirror_name):
    """
    Collect logs for a mirror.
    
    Returns:
        - Last successful log content (or None if no success)
        - List of (filepath, content) tuples for all failed logs
    """
    log_dir = LOG_BASE / mirror_name
    
    if not log_dir.exists():
        raise FileNotFoundError("Log directory not found: {}".format(log_dir))
    
    logs = []
    for entry in log_dir.iterdir():
        if entry.is_file():
            try:
                _, timestamp, is_fail = parse_log_filename(entry.name)
                logs.append((entry, timestamp, is_fail))
            except ValueError:
                continue
    
    if not logs:
        raise ValueError("No valid log files found in {}".format(log_dir))
    
    # Sort by timestamp descending (newest first)
    logs.sort(key=lambda x: x[1], reverse=True)
    
    # Find last successful log (first non-.fail when sorted desc)
    last_success = None
    failed_logs = []
    
    for filepath, timestamp, is_fail in logs:
        if is_fail:
            failed_logs.append((filepath, timestamp))
        elif last_success is None:
            last_success = (filepath, timestamp)
    
    # Read contents
    success_content = None
    if last_success:
        success_content = read_log_tail(last_success[0])
    
    failed_contents = []
    for filepath, _ in failed_logs:
        failed_contents.append((filepath, read_log_tail(filepath)))
    
    return success_content, failed_contents


def build_prompt(mirror_name, success_log, failed_logs):
    """Build the analysis prompt for the LLM."""
    prompt = """You are analyzing mirror sync failures for '{}'.

Your task is to identify the root cause(s) of the failures and suggest actionable fixes.

""".format(mirror_name)
    
    if success_log:
        prompt += """## Last Successful Sync Log (for reference)
This shows what a successful run looks like. Compare with failures below.

```
{}
```

""".format(success_log.strip())
    else:
        prompt += """## No Successful Log Found
No recent successful sync log available for comparison.

"""
    
    prompt += "## Failed Sync Logs ({} failures)\n".format(len(failed_logs))
    prompt += "Analyze these failures and identify common patterns or root causes.\n\n"
    
    for i, (filepath, content) in enumerate(failed_logs, 1):
        prompt += "### Failure {}: {}\n```\n{}\n```\n\n".format(
            i, filepath.name, content.strip()
        )
    
    prompt += """## Required Analysis

**IMPORTANT**: 
1. Your entire response MUST be under 4096 characters to fit Telegram's message limit.
2. Use Telegram Markdown style: `*bold*`, `_italic_`, `` `monospace` ``. Do NOT use `#` headers or `---` dividers.

1. **Root Cause**: What is causing the sync to fail? Be specific about the error mechanism.

2. **Evidence**: Which log entries support your conclusion?

3. **Fix Recommendation**: What concrete steps should be taken to resolve this? Include:
   - Configuration changes
   - Upstream issues to check
   - Commands to run for diagnosis
   - Script modifications if needed

4. **Severity**: Is this likely a transient issue, upstream problem, or local configuration breakage?

Be concise but thorough. Focus on actionable diagnostics and fixes. Keep your response under 4096 characters.
"""
    
    return prompt


def call_lllm(prompt):
    """Send prompt to LLM endpoint and return response."""
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer {}".format(API_KEY),
    }
    
    payload = {
        "model": LLM_MODEL,
        "messages": [
            {"role": "user", "content": prompt}
        ],
        "stream": False,
    }
    
    resp = requests.post(LLM_ENDPOINT, json=payload, headers=headers, timeout=120)
    resp.raise_for_status()
    
    result = resp.json()
    return result["choices"][0]["message"]["content"]


def send_telegram(message, log_attachment=None):
    """Send message to Telegram bot with optional log file attachment."""
    if not TELEGRAM_CHAT_ID:
        print("Warning: TELEGRAM_CHAT_ID not set, skipping Telegram notification", file=sys.stderr)
        return

    # Telegram has a 4096 character limit for text
    max_len = 4096
    if len(message) > max_len:
        truncation_note = "\n\n...(truncated)"
        message = message[:max_len - len(truncation_note)] + truncation_note

    try:
        # Send text message first
        resp = requests.post(
            TELEGRAM_API,
            json={"chat_id": TELEGRAM_CHAT_ID, "text": message, "parse_mode": "Markdown"},
            timeout=60
        )
        resp.raise_for_status()
        print("Telegram notification sent", file=sys.stderr)

        # Send log file separately if provided
        if log_attachment:
            log_content, log_filename = log_attachment
            send_document(log_content, log_filename)
    except requests.RequestException as e:
        print("Failed to send Telegram notification: {}".format(e), file=sys.stderr)


def send_document(log_content, log_filename):
    """Send document separately."""
    api_url = "https://botapi.dawn.ee/bot{}/sendDocument".format(TELEGRAM_BOT_TOKEN)
    try:
        resp = requests.post(
            api_url,
            data={"chat_id": TELEGRAM_CHAT_ID},
            files={"document": (log_filename, log_content, "text/plain")},
            timeout=60
        )
        resp.raise_for_status()
        print("Log file sent: {}".format(log_filename), file=sys.stderr)
    except requests.RequestException as e:
        print("Failed to send log file: {}".format(e), file=sys.stderr)


def main():
    if len(sys.argv) != 2:
        print("Usage: {} <mirror_name>".format(sys.argv[0]), file=sys.stderr)
        sys.exit(1)
    
    mirror_name = sys.argv[1]
    
    try:
        success_log, failed_logs = collect_logs(mirror_name)
    except (FileNotFoundError, ValueError) as e:
        error_msg = "Error collecting logs: {}".format(e)
        print(error_msg, file=sys.stderr)
        send_telegram("❌ Mirror Analysis Error\n\nMirror: `{}`\n\n{}".format(mirror_name, error_msg))
        sys.exit(1)
    
    if not failed_logs:
        print("No failed logs found for mirror '{}'".format(mirror_name))
        sys.exit(0)
    
    print("Analyzing {} failed log(s) for '{}'...".format(len(failed_logs), mirror_name), file=sys.stderr)
    if success_log:
        print("Including last successful log for comparison", file=sys.stderr)
    
    prompt = build_prompt(mirror_name, success_log, failed_logs)
    
    try:
        analysis = call_lllm(prompt)
    except requests.RequestException as e:
        error_msg = "LLM request failed: {}".format(e)
        print(error_msg, file=sys.stderr)
        send_telegram("❌ Mirror Analysis Error\n\nMirror: `{}`\n\n{}".format(mirror_name, error_msg))
        sys.exit(1)
    
    # Send to Telegram with last failed log as attachment
    telegram_msg = "⚠️ Mirror Sync Failure Analysis\n\nMirror: `{}`\nFailures: {}\n\n{}".format(
        mirror_name, len(failed_logs), analysis
    )
    # Attach the most recent failed log (first in the list since sorted by time desc)
    last_failed_log = None
    last_failed_path = None
    if failed_logs:
        last_failed_path, _ = failed_logs[0]
        with open(last_failed_path, "r", encoding="utf-8", errors="replace") as f:
            last_failed_log = f.read()
    send_telegram(telegram_msg, log_attachment=(last_failed_log, last_failed_path.name if last_failed_path else "failed.log"))
    
    # Also print to stdout
    print("\n" + "=" * 60)
    print("LLM Failure Analysis for '{}'".format(mirror_name))
    print("=" * 60 + "\n")
    print(analysis)


if __name__ == "__main__":
    main()
