/**
 * Guardrails Extension — Security & Redaction
 *
 * Ported from the pi community "agents" extension pack.
 *
 * Features:
 * - Redacts secrets from tool results before the LLM sees them
 * - Blocks dangerous bash commands (rm -rf, sudo, mkfs, etc.)
 * - Blocks writes to protected paths (.env, .git, .ssh, etc.)
 */

import type { ExtensionAPI, ExtensionContext } from "@singularity-forge/pi-coding-agent";
import * as path from "node:path";

// ============================================================================
// Secret Redaction
// ============================================================================

interface RedactionRule {
  pattern: RegExp;
  replacement: string;
}

const SENSITIVE_PATTERNS: RedactionRule[] = [
  { pattern: /\b(sk-[a-zA-Z0-9]{20,})\b/g, replacement: "[OPENAI_KEY_REDACTED]" },
  { pattern: /\b(ghp_[a-zA-Z0-9]{36,})\b/g, replacement: "[GITHUB_TOKEN_REDACTED]" },
  { pattern: /\b(gho_[a-zA-Z0-9]{36,})\b/g, replacement: "[GITHUB_OAUTH_REDACTED]" },
  { pattern: /\b(xox[baprs]-[a-zA-Z0-9-]{10,})\b/g, replacement: "[SLACK_TOKEN_REDACTED]" },
  { pattern: /\b(AKIA[A-Z0-9]{16})\b/g, replacement: "[AWS_KEY_REDACTED]" },
  {
    pattern: /\b(api[_-]?key|apikey)\s*[=:]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi,
    replacement: "$1=[REDACTED]",
  },
  {
    pattern: /\b(secret|token|password|passwd|pwd)\s*[=:]\s*['"]?([^\s'"]{8,})['"]?/gi,
    replacement: "$1=[REDACTED]",
  },
  { pattern: /\b(bearer)\s+([a-zA-Z0-9._-]{20,})\b/gi, replacement: "Bearer [REDACTED]" },
  { pattern: /(mongodb(\+srv)?:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
  { pattern: /(postgres(ql)?:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
  { pattern: /(mysql:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
  { pattern: /(redis:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
  {
    pattern: /-----BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END \1PRIVATE KEY-----/g,
    replacement: "[PRIVATE_KEY_REDACTED]",
  },
];

const SENSITIVE_FILES: { pattern: RegExp; desc: string }[] = [
  { pattern: /\.env$/, desc: ".env" },
  { pattern: /\.env\.(?!example$)[^/]+$/, desc: ".env local/override" },
  { pattern: /\.dev\.vars($|\.[ˆ/]+$)/, desc: ".dev.vars" },
  { pattern: /secrets?\.(json|ya?ml|toml)$/i, desc: "secrets file" },
  { pattern: /credentials/i, desc: "credentials file" },
];

function redactToolResult(toolName: string, filePath: string | undefined, text: string, ctx: ExtensionContext): { content: [{ type: "text"; text: string }] } | undefined {
  if (toolName === "read" && filePath) {
    if (/(^|\/)\.env\.example$/i.test(filePath)) {
      return undefined;
    }
    for (const { pattern, desc } of SENSITIVE_FILES) {
      if (pattern.test(filePath)) {
        ctx.ui.notify(`🔒 Redacted contents of sensitive file: ${filePath}`, "info");
        return {
          content: [{ type: "text", text: `[Contents of ${desc} (${filePath}) redacted for security]` }],
        };
      }
    }
  }

  let result = text;
  let modified = false;
  for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
    const next = result.replace(pattern, replacement);
    if (next !== result) {
      modified = true;
      result = next;
    }
  }

  if (modified) {
    ctx.ui.notify("🔒 Sensitive data redacted from output", "info");
    return { content: [{ type: "text", text: result }] };
  }

  return undefined;
}

// ============================================================================
// Command & Path Security
// ============================================================================

interface DangerousCommand {
  pattern: RegExp;
  desc: string;
}

const DANGEROUS_COMMANDS: DangerousCommand[] = [
  { pattern: /\brm\s+(-[^\s]*r|--recursive)/, desc: "recursive delete" },
  { pattern: /\bsudo\b/, desc: "sudo command" },
  { pattern: /\b(chmod|chown)\b.*777/, desc: "dangerous permissions" },
  { pattern: /\bmkfs\b/, desc: "filesystem format" },
  { pattern: /\bdd\b.*\bof=\/dev\//, desc: "raw device write" },
  { pattern: />\s*\/dev\/sd[a-z]/, desc: "raw device overwrite" },
  { pattern: /\bkill\s+-9\s+-1\b/, desc: "kill all processes" },
  { pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/, desc: "fork bomb" },
];

const PROTECTED_PATHS: { pattern: RegExp; desc: string }[] = [
  { pattern: /\.env($|\.(?!example))/, desc: "environment file" },
  { pattern: /\.dev\.vars($|\.[ˆ/]+$)/, desc: "dev vars file" },
  { pattern: /node_modules\//, desc: "node_modules" },
  { pattern: /^\.git\/|\/\.git\//, desc: "git directory" },
  { pattern: /\.pem$|\.key$/, desc: "private key file" },
  { pattern: /id_rsa|id_ed25519|id_ecdsa/, desc: "SSH key" },
  { pattern: /\.ssh\//, desc: ".ssh directory" },
  { pattern: /secrets?\.(json|ya?ml|toml)$/i, desc: "secrets file" },
  { pattern: /credentials/i, desc: "credentials file" },
];

const SOFT_PROTECTED_PATHS: { pattern: RegExp; desc: string }[] = [
  { pattern: /package-lock\.json$/, desc: "package-lock.json" },
  { pattern: /yarn\.lock$/, desc: "yarn.lock" },
  { pattern: /pnpm-lock\.yaml$/, desc: "pnpm-lock.yaml" },
];

const DANGEROUS_BASH_WRITES: RegExp[] = [
  />\s*\.env(?!\.example)(\b|$)/,
  />\s*\.dev\.vars/,
  />\s*.*\.pem/,
  />\s*.*\.key/,
  /tee\s+.*\.env(?!\.example)(\b|$)/,
  /tee\s+.*\.dev\.vars/,
  /cp\s+.*\s+\.env(?!\.example)(\b|$)/,
  /mv\s+.*\s+\.env(?!\.example)(\b|$)/,
];

async function checkBashCommand(command: string, ctx: ExtensionContext): Promise<{ block: true; reason: string } | undefined> {
  for (const { pattern, desc } of DANGEROUS_COMMANDS) {
    if (pattern.test(command)) {
      if (!ctx.hasUI) {
        return { block: true, reason: `Blocked ${desc} (no UI to confirm)` };
      }
      const ok = await ctx.ui.confirm(`⚠️ Dangerous command: ${desc}`, command);
      if (!ok) {
        return { block: true, reason: `Blocked ${desc} by user` };
      }
      break;
    }
  }

  for (const pattern of DANGEROUS_BASH_WRITES) {
    if (pattern.test(command)) {
      ctx.ui.notify("🛡️ Blocked bash write to protected path", "warning");
      return { block: true, reason: "Bash command writes to protected path" };
    }
  }

  return undefined;
}

async function checkWritePath(filePath: string, ctx: ExtensionContext): Promise<{ block: true; reason: string } | undefined> {
  const normalized = path.normalize(filePath);

  for (const { pattern, desc } of PROTECTED_PATHS) {
    if (pattern.test(normalized)) {
      ctx.ui.notify(`🛡️ Blocked write to ${desc}: ${filePath}`, "warning");
      return { block: true, reason: `Protected path: ${desc}` };
    }
  }

  for (const { pattern, desc } of SOFT_PROTECTED_PATHS) {
    if (pattern.test(normalized)) {
      if (!ctx.hasUI) {
        return { block: true, reason: `Protected path (no UI): ${desc}` };
      }
      const ok = await ctx.ui.confirm(`⚠️ Modifying ${desc}`, `Are you sure you want to modify ${filePath}?`);
      if (!ok) {
        return { block: true, reason: `User blocked write to ${desc}` };
      }
      break;
    }
  }

  return undefined;
}

// ============================================================================
// Safe Git
// ============================================================================

type PromptLevel = "high" | "medium" | "none";
type Severity = "high" | "medium";

interface SafeGitConfig {
  promptLevel?: PromptLevel;
  enabledByDefault?: boolean;
}

const SAFE_GIT_DEFAULTS: Required<SafeGitConfig> = {
  promptLevel: "medium",
  enabledByDefault: true,
};

const GIT_PATTERNS: { pattern: RegExp; action: string; severity: Severity }[] = [
  // High risk
  { pattern: /\bgit\s+push\s+.*--force(-with-lease)?\b/i, action: "force push", severity: "high" },
  { pattern: /\bgit\s+reset\s+--hard\b/i, action: "hard reset", severity: "high" },
  { pattern: /\bgit\s+clean\s+-[a-z]*f/i, action: "clean (remove untracked files)", severity: "high" },
  { pattern: /\bgit\s+stash\s+(drop|clear)\b/i, action: "drop/clear stash", severity: "high" },
  { pattern: /\bgit\s+branch\s+-[dD]\b/i, action: "delete branch", severity: "high" },
  { pattern: /\bgit\s+reflog\s+expire\b/i, action: "expire reflog", severity: "high" },
  // Medium risk
  { pattern: /\bgit\s+push\b/i, action: "push", severity: "medium" },
  { pattern: /\bgit\s+commit\b/i, action: "commit", severity: "medium" },
  { pattern: /\bgit\s+rebase\b/i, action: "rebase", severity: "medium" },
  { pattern: /\bgit\s+merge\b/i, action: "merge", severity: "medium" },
  { pattern: /\bgit\s+tag\b/i, action: "create/modify tag", severity: "medium" },
  { pattern: /\bgit\s+cherry-pick\b/i, action: "cherry-pick", severity: "medium" },
  { pattern: /\bgit\s+revert\b/i, action: "revert", severity: "medium" },
  { pattern: /\bgit\s+am\b/i, action: "apply patches", severity: "medium" },
  // GitHub CLI
  { pattern: /\bgh\s+\S+/i, action: "GitHub CLI", severity: "medium" },
];

const severityIcons: Record<Severity, string> = {
  high: "🔴",
  medium: "🟡",
};

function getSafeGitConfig(
  ctx: ExtensionContext,
  enabledOverride?: boolean | null,
  promptLevelOverride?: PromptLevel | null,
): { enabled: boolean; promptLevel: PromptLevel } {
  const settings = (ctx as any).settingsManager?.getSettings() ?? {};
  const config: Required<SafeGitConfig> = { ...SAFE_GIT_DEFAULTS, ...(settings.safeGit ?? {}) };
  return {
    enabled: enabledOverride !== null && enabledOverride !== undefined ? enabledOverride : config.enabledByDefault,
    promptLevel: promptLevelOverride !== null && promptLevelOverride !== undefined ? promptLevelOverride : config.promptLevel,
  };
}

function shouldPrompt(severity: Severity, promptLevel: PromptLevel): boolean {
  if (promptLevel === "none") return false;
  if (promptLevel === "high") return severity === "high";
  return true;
}

async function checkGitCommand(
  command: string,
  ctx: ExtensionContext,
  sessionApprovedActions: Set<string>,
  sessionBlockedActions: Set<string>,
  enabledOverride?: boolean | null,
  promptLevelOverride?: PromptLevel | null,
): Promise<{ block: true; reason: string } | undefined> {
  const { enabled, promptLevel } = getSafeGitConfig(ctx, enabledOverride, promptLevelOverride);
  if (!enabled || promptLevel === "none") return undefined;

  for (const { pattern, action, severity } of GIT_PATTERNS) {
    if (pattern.test(command)) {
      if (sessionBlockedActions.has(action)) {
        ctx.ui.notify(`🚫 Git ${action} auto-blocked (session setting)`, "warning");
        return { block: true, reason: `Git ${action} blocked by user (session setting)` };
      }
      if (sessionApprovedActions.has(action)) {
        ctx.ui.notify(`✅ Git ${action} auto-approved (session setting)`, "info");
        return undefined;
      }
      if (!shouldPrompt(severity, promptLevel)) {
        return undefined;
      }

      const icon = severityIcons[severity];
      if (!ctx.hasUI) {
        return { block: true, reason: `Git ${action} blocked: requires explicit user approval (no UI available)` };
      }

      const title =
        severity === "high"
          ? `${icon} ⚠️ HIGH RISK: Git ${action} requires approval`
          : `${icon} Git ${action} requires approval`;

      const choice = await ctx.ui.select(title, [
        "✅ Allow this command once",
        "⏭️  Decline this time (ask again later)",
        `✅✅ Auto-approve all "git ${action}" for this session only`,
        `🚫 Auto-block all "git ${action}" for this session only`,
      ]);
      if (typeof choice !== "string") return { block: true, reason: `Git ${action} declined by user` };

      if (!choice || choice.startsWith("⏭️")) {
        ctx.ui.notify(`Git ${action} declined`, "info");
        return { block: true, reason: `Git ${action} declined by user` };
      }
      if (choice.startsWith("🚫")) {
        sessionBlockedActions.add(action);
        ctx.ui.notify(`🚫 All "git ${action}" commands auto-blocked for this session`, "warning");
        return { block: true, reason: `Git ${action} blocked by user (session setting)` };
      }
      if (choice.startsWith("✅✅")) {
        sessionApprovedActions.add(action);
        ctx.ui.notify(`✅ All "git ${action}" commands auto-approved for this session`, "info");
      } else {
        ctx.ui.notify(`Git ${action} approved once`, "info");
      }
      return undefined;
    }
  }

  return undefined;
}

function registerSafeGitCommands(
  pi: ExtensionAPI,
  sessionEnabledOverride: { value: boolean | null },
  sessionPromptLevelOverride: { value: PromptLevel | null },
) {
  pi.registerCommand("safegit", {
    description: "Toggle safe-git protection on/off for this session",
    handler: async (_, ctx) => {
      const { enabled } = getSafeGitConfig(ctx, sessionEnabledOverride.value, sessionPromptLevelOverride.value);
      sessionEnabledOverride.value = !enabled;
      ctx.ui.notify(
        sessionEnabledOverride.value ? "🔒 Safe-git protection ON" : "🔓 Safe-git protection OFF",
        "info",
      );
      ctx.ui.notify("(Temporary for this session)", "info");
    },
  });

  pi.registerCommand("safegit-level", {
    description: "Set prompt level: high, medium, or none",
    handler: async (args, ctx) => {
      const arg = typeof args === "string" ? args.trim().toLowerCase() : "";
      if (arg === "high" || arg === "medium" || arg === "none") {
        sessionPromptLevelOverride.value = arg;
        const desc = {
          high: "🔴 Only high-risk operations require approval",
          medium: "🟡 Medium and high-risk operations require approval",
          none: "⚠️ No approval required (protection disabled)",
        };
        ctx.ui.notify(`Prompt level: ${arg}`, "info");
        ctx.ui.notify(desc[arg], "info");
        ctx.ui.notify("(Temporary for this session)", "info");
        return;
      }

      const { promptLevel } = getSafeGitConfig(ctx, sessionEnabledOverride.value, sessionPromptLevelOverride.value);
      const options = [
        `🔴 high - Only high-risk (force push, hard reset, etc.)`,
        `🟡 medium - Medium and high-risk (push, commit, etc.)`,
        `⚠️ none - No prompts (disable protection)`,
        `❌ Cancel`,
      ];

      ctx.ui.notify(`Current level: ${promptLevel}\n`, "info");
      const choice = await ctx.ui.select("Set prompt level:", options);
      const selectedChoice = typeof choice === "string" ? choice : undefined;
      if (!selectedChoice || selectedChoice.startsWith("❌")) {
        ctx.ui.notify("Cancelled.", "info");
        return;
      }
      const level = selectedChoice.split(" ")[1] as PromptLevel;
      sessionPromptLevelOverride.value = level;
      ctx.ui.notify(`Prompt level set to: ${selectedChoice}`, "info");
      ctx.ui.notify("(Temporary for this session)", "info");
    },
  });

  pi.registerCommand("safegit-status", {
    description: "Show safe-git status and settings",
    handler: async (_, ctx) => {
      const settings = (ctx as any).settingsManager?.getSettings() ?? {};
      const globalConfig: Required<SafeGitConfig> = {
        ...SAFE_GIT_DEFAULTS,
        ...(settings.safeGit ?? {}),
      };
      const { enabled, promptLevel } = getSafeGitConfig(ctx, sessionEnabledOverride.value, sessionPromptLevelOverride.value);

      const lines = [
        "─── Safe Git Status ───",
        "",
        "Session State:",
        `  Enabled: ${enabled ? "🔒 ON" : "🔓 OFF"}${sessionEnabledOverride.value !== null ? " (session override)" : ""}`,
        `  Prompt Level: ${promptLevel}${sessionPromptLevelOverride.value !== null ? " (session override)" : ""}`,
        "",
        "Global Defaults:",
        `  Enabled: ${globalConfig.enabledByDefault ? "ON" : "OFF"}`,
        `  Prompt Level: ${globalConfig.promptLevel}`,
        "",
        "Prompt Levels:",
        `  🔴 high   - force push, hard reset, clean, delete branch`,
        `  🟡 medium - push, commit, rebase, merge, tag, gh CLI`,
        "",
        "Commands: /safegit /safegit-level /safegit-status",
        "───────────────────────",
      ];
      ctx.ui.notify(lines.join("\n"), "info");
    },
  });
}

// ============================================================================
// Entry Point
// ============================================================================

export default function guardrails(pi: ExtensionAPI): void {
  const sessionApprovedActions = new Set<string>();
  const sessionBlockedActions = new Set<string>();
  const sessionEnabledOverride: { value: boolean | null } = { value: null };
  const sessionPromptLevelOverride: { value: PromptLevel | null } = { value: null };

  registerSafeGitCommands(pi, sessionEnabledOverride, sessionPromptLevelOverride);

  pi.on("session_start", async (_, ctx) => {
    sessionEnabledOverride.value = null;
    sessionPromptLevelOverride.value = null;
    sessionApprovedActions.clear();
    sessionBlockedActions.clear();

    const { enabled, promptLevel } = getSafeGitConfig(ctx, sessionEnabledOverride.value, sessionPromptLevelOverride.value);
    if (ctx.hasUI && enabled && promptLevel !== "none") {
      const promptDesc = promptLevel === "high" ? "🔴 high-risk only" : "🟡 medium+high";
      ctx.ui.notify(`Safe-git: Protection ${promptDesc}`, "info");
    }
  });

  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "bash") {
      const command = event.input.command as string;
      const gitResult = await checkGitCommand(command, ctx, sessionApprovedActions, sessionBlockedActions, sessionEnabledOverride.value, sessionPromptLevelOverride.value);
      if (gitResult) return gitResult;
      return checkBashCommand(command, ctx);
    }

    if (event.toolName === "write" || event.toolName === "edit") {
      const filePath = event.input.path as string;
      return checkWritePath(filePath, ctx);
    }

    return undefined;
  });

  pi.on("tool_result", async (event, ctx) => {
    if (event.isError) return undefined;

    const textContent = event.content.find(
      (c): c is { type: "text"; text: string } => c.type === "text",
    );
    if (!textContent) return undefined;

    return redactToolResult(event.toolName, event.input.path as string | undefined, textContent.text, ctx);
  });
}
