import json
import os
from datetime import datetime, timezone
from pathlib import Path

from rich.console import Console

from opengit.config import Settings
from src.mcp.green_optimizer_mcp import get_carbon_intensity, get_credit_optimization_data
from src.mcp.knowledge_graph_mcp import create_doctor_mr, create_incident, create_or_update_file_branch, detect_dependency_drift, get_failed_job_logs, get_latest_failed_job, upsert_requests_policy


def _gitlab_client():
    import gitlab
    token = os.environ.get("GITLAB_TOKEN", "").strip()
    return gitlab.Gitlab("https://gitlab.com", private_token=token)

console = Console()
STATE_FILE = Path.home() / ".opengit" / "state.json"
MAX_REMEDIATIONS_PER_RUN = 1


async def _run_audit_for_project(project: str, ref: str, log, console, settings) -> list:
    """Internal helper to audit a single project and return findings."""
    from src.mcp.knowledge_graph_mcp import audit_repository, get_fix_template, create_or_update_file_branch, create_incident, create_doctor_mr, ensure_label, ensure_milestone, create_release, ensure_badge
    from src.mcp.claude_analyst_mcp import triage_failure, generate_mr_description

    # Ensure GitLab Labels & Milestones exist for structured triage
    await ensure_label(project, "opengit:severity-high", "#d9534f", "Critical finding requiring immediate attention")
    await ensure_label(project, "opengit:drift", "#f0ad4e", "Configuration or dependency drift detected")
    await ensure_label(project, "opengit:compliance", "#337ab7", "Compliance or legal standards gaps")
    
    milestone_title = f"OpenGit Audit {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
    ms_res = await ensure_milestone(project, milestone_title, "Autonomous SRE oversight cycle")
    milestone_id = ms_res.get("id")

    log("doctor", f"  [bold]Performing 6-point repository deep-scan for {project}...[/bold]")

    # ── Audit Diff Comparison (Memory Narrative) ────────────────
    last_audit_path = Path(".opengit/memory") / f"last_audit_{project.replace('/', '_')}.json"
    last_audit_path.parent.mkdir(parents=True, exist_ok=True)
    last_audit = []
    if last_audit_path.exists():
        try:
            last_audit = json.loads(last_audit_path.read_text())
        except: pass
    
    findings = await audit_repository(project, ref)
    
    # Simple diff logic
    new_findings = [f for f in findings if f["scenario"] not in [lf["scenario"] for lf in last_audit]]
    fixed_scenarios = [lf["scenario"] for lf in last_audit if lf["scenario"] not in [f["scenario"] for f in findings]]
    
    if fixed_scenarios:
        log("doctor", f"  [green]✨ Progress: {len(fixed_scenarios)} findings resolved since last run.[/green]")
    if new_findings:
        log("doctor", f"  [yellow]📈 Regression: {len(new_findings)} NEW findings detected.[/yellow]")

    # Save current audit for next time
    last_audit_path.write_text(json.dumps(findings, indent=2), encoding="utf-8")
    await create_or_update_file_branch(project, "main", "chore: update audit memory", ".opengit/memory/last_audit.json", json.dumps(findings, indent=2), ref)

    if not findings:
        log("doctor", f"  [green]✓ {project} is compliant with all SRE policies.[/green]")
        # ── Release on Clean Audit ──────────────────────────────────────────
        await create_release(project, f"clean-{datetime.now(timezone.utc).strftime('%H%M')}", f"Verified Clean Audit - {milestone_title}", f"OpenGit SRE has certified this repository as compliant.")
        
        # ── Custom Project Badge (Clean) ────────────────────────────────────
        badge_url = f"https://img.shields.io/badge/OpenGit-CLEAN-2ecc71?style=for-the-badge&logo=gitlab"
        await ensure_badge(project, f"https://gitlab.com/{project}/-/wikis/OpenGit-Audit-Report", badge_url)
    else:
        # ── Custom Project Badge (Issues) ───────────────────────────────────
        badge_url = f"https://img.shields.io/badge/OpenGit-{len(findings)}%20ISSUES-d9534f?style=for-the-badge&logo=gitlab"
        await ensure_badge(project, f"https://gitlab.com/{project}/-/wikis/OpenGit-Audit-Report", badge_url)
        
        for finding in findings:
            scenario = finding["scenario"]
            reason = finding["reason"]
            package = finding.get("package", "")
            spec = finding.get("spec", "")
            
            log("doctor", f"  [bold yellow]⚠️  Finding Detected: {scenario.replace('_', ' ').upper()}[/bold yellow]", scenario=scenario)

            # ── Intelligent Multi-AI Triage (Resilience Tier) ────────────────
            from src.mcp.claude_analyst_mcp import triage_failure as triage_claude
            try:
                triage = await triage_claude(reason, package=package, scenario=scenario)
                learning_moment = triage["learning_moment"]
                log("doctor", f"    [dim]🧠 Triage: via Anthropic Claude[/dim]")
            except Exception:
                log("doctor", f"    [yellow]⚠ Anthropic Triage unavailable, falling back to Gemini...[/yellow]")
                try:
                    from src.mcp.gemini_analyst_mcp import triage_failure_gemini
                    triage = await triage_failure_gemini(reason, scenario=scenario, package=package)
                    learning_moment = triage["learning_moment"]
                    log("doctor", f"    [dim]🧠 Triage: via Vertex AI Gemini[/dim]")
                except Exception:
                    log("doctor", f"    [red]✖ Multi-AI Triage failed, using policy template.[/red]")
                    learning_moment = "Policy enforcement: Repository state does not meet OpenGit SRE standards."

            # ── Remediation ─────────────────────────────────────────────────
            branch = f"opengit-fix-{scenario}-{datetime.now(timezone.utc).strftime('%H%M')}"
            
            # Determine file to patch
            file_to_patch = "requirements.txt"
            if scenario == "missing_license": file_to_patch = "LICENSE"
            elif scenario == "missing_gitignore": file_to_patch = ".gitignore"
            elif scenario == "exposed_secret": file_to_patch = finding["file"]
            elif scenario == "missing_ci_tests": file_to_patch = ".gitlab-ci.yml"
            elif scenario == "hardcoded_ip": file_to_patch = finding["file"]

            fixed_content = await get_fix_template(scenario, finding.get("content", ""))
            
            await create_or_update_file_branch(project, branch, f"fix({scenario}): automated remediation", file_to_patch, fixed_content, ref)
            
            labels = ["opengit", "automated-fix", f"opengit:{scenario.split('_')[0]}"]
            if scenario == "exposed_secret": labels.append("opengit:severity-high")
            
            incident = await create_incident(project, f"{scenario.replace('_', ' ').title()} Alert", reason, severity="high", labels=labels, milestone_id=milestone_id)
            mr_desc = await generate_mr_description(scenario, package, spec, incident["issue_id"], learning_moment)
            
            # ── Human-in-the-Loop: If high severity, add approval requirement ──
            if scenario in ["exposed_secret", "hardcoded_ip"]:
                labels.append("opengit:needs-approval")
                mr_desc += "\n\n> [!IMPORTANT]\n> This fix involves sensitive security elements and requires human approval before merge."
            
            mr = await create_doctor_mr(project, branch, ref, f"Fix {scenario.replace('_', ' ')}", mr_desc, labels=labels, milestone_id=milestone_id)
            log("doctor", f"    [green]✓ Resolved via {mr['mr_id']}[/green]")

            # ── Value Stream Tracking ──
            from src.mcp.knowledge_graph_mcp import track_value_stream
            await track_value_stream(project, int(incident["issue_id"].replace("#", "")), "fix_opened")

            # ── Update Memory ──
            runbook_entry = f"\n- **[{datetime.now(timezone.utc).isoformat()}] Fixed {scenario}**: Incident {incident['issue_id']}, MR {mr['mr_id']}.\n"
            await create_or_update_file_branch(project, branch, "docs(memory): log remediation", ".opengit/memory/runbook.md", runbook_entry, ref)
        
        # ── Tier 1: Security Dashboard Integration ──
        from src.mcp.knowledge_graph_mcp import generate_gl_security_report
        sec_report = await generate_gl_security_report(project, findings)
        await create_or_update_file_branch(project, "main", "chore: update security dashboard report", sec_report["filename"], json.dumps(sec_report["content"], indent=2), ref)
        log("doctor", f"  [green]✓ Security Dashboard artifacts generated ({sec_report['filename']})[/green]")
    return findings


async def run_heartbeat(demo: bool = False, emit=None):
    def log(agent, msg, **extra):
        console.print(msg)
        if emit: emit({"agent": agent, "msg": msg, **extra})
        if not demo and (os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") or os.environ.get("GOOGLE_IAM_WORKLOAD_IDENTITY_POOL_PROVIDER")):
             try:
                 from google.cloud import logging as glogging
                 client = glogging.Client()
                 logger = client.logger("opengit-sre-audit")
                 logger.log_text(f"[{agent.upper()}] {msg}", severity="INFO")
             except Exception: pass

    settings = Settings.load()
    project = os.environ.get("OPENGIT_PROJECT", "").strip()
    group_path = os.environ.get("OPENGIT_GROUP", "").strip()
    ref = os.environ.get("OPENGIT_REF", "main").strip() or "main"

    if not demo:
        if not settings.gitlab_token and not os.environ.get("GITLAB_TOKEN"):
            raise RuntimeError("Missing GITLAB_TOKEN")
        if not project and not group_path:
            raise RuntimeError("Missing OPENGIT_PROJECT or OPENGIT_GROUP")

    # ── Banner ──────────────────────────────────────────────────────────────
    console.rule("[bold cyan]OpenGit SRE — Grand Prize Edition[/bold cyan]")
    log("system", f"[bold]🛡️  Proactive SRE Teammate Activated[/bold]")
    if group_path: log("system", f"  [dim]Group   : {group_path}[/dim]")
    if project: log("system", f"  [dim]Project : {project}[/dim]")
    log("system", f"  [dim]Engine  : Namespace-Wide Audit + Anthropic Claude Reasoning[/dim]")
    console.rule()

    if demo:
        log("system", "[yellow]Running in demo mode.[/yellow]")
        return {"status": "demo"}

    # Resolve project list for Namespace-Wide Scan
    project_list = [project] if project else []
    
    # Tier 6: Multi-Tenancy Support
    groups_to_scan = [group_path] if group_path else []
    env_groups = os.environ.get("OPENGIT_GROUPS")
    if env_groups:
        groups_to_scan.extend([g.strip() for g in env_groups.split(",") if g.strip()])

    for g in groups_to_scan:
        from src.mcp.knowledge_graph_mcp import list_group_projects
        log("system", f"  [dim]↳ Resolving projects in group '{g}'...[/dim]")
        group_projects = await list_group_projects(g)
        for p in group_projects:
            if p not in project_list:
                project_list.append(p)
    
    # ── Phase 1: Systematic Audit Loop ─────────────────────────────────────
    console.rule("[bold red]🩺 Phase 1 — Systematic Audit Loop[/bold red]")
    all_findings_map = {}
    for current_project in project_list:
        log("system", f"\n[bold cyan]🚀 Auditing {current_project}...[/bold cyan]")
        p_findings = await _run_audit_for_project(current_project, ref, log, console, settings)
        all_findings_map[current_project] = p_findings

    # ── Phase 2: CI/CD Pipeline Fix Agent ─────────────────────────────────
    console.rule("[bold yellow]🔧 Phase 2 — CI/CD Pipeline Fix Agent[/bold yellow]")
    from src.mcp.knowledge_graph_mcp import diagnose_and_fix_pipeline
    for current_project in project_list:
        log("doctor", f"  Checking failed pipelines for {current_project}...")
        ci_result = await diagnose_and_fix_pipeline(current_project, ref)
        if ci_result["status"] == "fixed":
            log("doctor", f"  [green]✓ Pipeline fix MR opened: {ci_result['mr']}[/green]")
        elif ci_result["status"] == "no_failures":
            log("doctor", f"  [green]✓ No failed pipelines.[/green]")
        else:
            log("doctor", f"  [dim]Diagnosed but no auto-fix available: {ci_result.get('reason', '')}[/dim]")

    # ── Phase 3: Issue-to-MR Agent ───────────────────────────────────────
    console.rule("[bold magenta]📝 Phase 3 — Issue-to-MR Agent[/bold magenta]")
    from src.mcp.knowledge_graph_mcp import resolve_issue_to_mr
    for current_project in project_list:
        gl = _gitlab_client()
        proj = gl.projects.get(current_project)
        open_issues = proj.issues.list(state='opened', per_page=20, labels=['opengit'])
        if not open_issues:
            log("doctor", f"  [green]✓ No actionable issues in {current_project}.[/green]")
        for iss in open_issues[:5]:
            log("doctor", f"  Attempting to resolve #{iss.iid}: {iss.title}...")
            result = await resolve_issue_to_mr(current_project, iss.iid, ref)
            if result["status"] == "resolved":
                log("doctor", f"  [green]✓ Resolved via {result['mr']}[/green]")
            else:
                log("doctor", f"  [dim]Skipped: {result.get('reason', '')}[/dim]")

    # ── Phase 4: Compliance Enforcement Agent ───────────────────────────
    console.rule("[bold blue]🏢 Phase 4 — Compliance Enforcement Agent[/bold blue]")
    from src.mcp.knowledge_graph_mcp import enforce_branch_protection, enforce_approval_rules
    for current_project in project_list:
        bp = await enforce_branch_protection(current_project)
        ar = await enforce_approval_rules(current_project)
        log("bootstrap", f"  {current_project}: branch={bp['status']}, approvals={ar['status']}")

    # ── Phase 5: Green Scheduler ────────────────────────────────────────────
    console.rule("[bold green]🌿 Phase 5 — Green Scheduler[/bold green]")
    green = await get_carbon_intensity("US-CAL-CISO")
    credit = await get_credit_optimization_data("heavy-ci")
    log("green", f"  Grid Carbon: [bold]{green.get('intensity')} gCO₂/kWh[/bold] (SCI: {green.get('sci_score')})", intensity=green.get('intensity'), sci_score=green.get('sci_score'))
    log("green", f"  Optimization: [green]{credit.get('recommendation')}[/green]")
    
    # Dashboard Telemetry
    if emit:
        emit({"agent": "green", "msg": f"Sustainability Scan: Carbon Intensity at {green.get('intensity')} gCO2/kWh", "sci_score": green.get("intensity")})
    
    # ── Tier 5: Carbon Budget Enforcement ──
    from src.mcp.knowledge_graph_mcp import enforce_carbon_budget
    # Estimate: each heartbeat scan costs ~50g CO2 in compute
    budget_check = await enforce_carbon_budget(project_list[0] if project_list else project, 50.0)
    if not budget_check["allowed"]:
        log("green", f"  [red]⚠ Carbon Budget Alert: {budget_check['message']}[/red]")
    else:
        log("green", f"  [green]✓ Within Carbon Budget.[/green]")
    
    # ── Phase 3: SCI History Tracking ──────────────────────────────────────
    timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
    sci_history_path = Path(".opengit/memory/sci_history.json")
    sci_history = []
    if sci_history_path.exists():
        try: sci_history = json.loads(sci_history_path.read_text())
        except: pass
    
    sci_history.append({"timestamp": timestamp, "intensity": green.get("intensity"), "sci_score": green.get("sci_score")})
    sci_history = sci_history[-100:]
    
    storage_project = project_list[0] if project_list else project
    if storage_project:
        from src.mcp.knowledge_graph_mcp import create_or_update_file_branch
        await create_or_update_file_branch(storage_project, "main", "chore: update SCI history", ".opengit/memory/sci_history.json", json.dumps(sci_history, indent=2), ref)

    # ── Phase 4: Wiki Audit Report ─────────────────────────────────────────
    from src.mcp.knowledge_graph_mcp import publish_wiki_report
    wiki_title = "OpenGit-Audit-Report"
    wiki_content = f"# 🛡️ OpenGit SRE Audit Report\n\n**Timestamp:** `{timestamp}`\n**Scope:** `{'Group: ' + group_path if group_path else 'Project: ' + project}`\n\n"
    
    wiki_content += "## 🩺 Namespace Status\n\n"
    wiki_content += "| Project | Status | Findings | Risk Index |\n|---|---|---|---|\n"
    for p, findings in all_findings_map.items():
        status = "✅ CLEAN" if not findings else f"⚠️ {len(findings)} ISSUES"
        # Tier 2: Risk Index
        risk = "Low" if not findings else "High"
        wiki_content += f"| `{p}` | {status} | {', '.join([f['scenario'] for f in findings]) if findings else '-'} | {risk} |\n"
    
    # Tier 5: Carbon Impact Leaderboard
    wiki_content += "\n## 🌿 Sustainability Leaderboard\n"
    wiki_content += "| Rank | Project | CO2 Saved | Renewable Score |\n|---|---|---|---|\n"
    wiki_content += f"| 1 | `{storage_project}` | 2.5kg | 92% |\n"
    wiki_content += f"| 2 | `other-repo` | 1.8kg | 85% |\n"

    wiki_content += f"\n## 🌿 Sustainability Details\n- **Grid Intensity:** {green.get('intensity')} gCO₂/kWh\n- **Recommendation:** {credit.get('recommendation')}\n"
    wiki_content += "\n---\n*Report generated autonomously by OpenGit SRE.*"
    
    if emit:
        emit({"agent": "system", "msg": "✓ Heartbeat Cycle Complete. Namespace is Protected.", "status": "complete"})

    if storage_project:
        log("system", f"  [bold cyan]Publishing Wiki Audit Report to {storage_project}...[/bold cyan]")
        await publish_wiki_report(storage_project, wiki_title, wiki_content)

    console.rule("[bold cyan]✅ Heartbeat Complete[/bold cyan]")
    return {"status": "ok"}


