/**
 * Pre-Review Self-Check
 *
 * Before sending work to Jake, check it against learned patterns.
 * Returns a "predicted score" and list of potential issues.
 *
 * From §6.1 of the design spec:
 * - Check each learned rule against the work product
 * - Predict Jake's score based on historical patterns
 * - Apply calibration correction to raw confidence
 * - Recommend self-fixes before submission
 */

import * as fs from "node:fs";
import * as path from "node:path";
import type {
  SelfCheckResult,
  SelfCheckViolation,
  CandidateRule,
  CalibrationCurve,
  DimensionTrend,
} from "../types.js";
import { applyCalibration } from "../analysis/calibration.js";

// ═══════════════════════════════════════════════════════════════
// TYPES
// ═══════════════════════════════════════════════════════════════

export interface WorkProductForCheck {
  /** Content to check (code, text, config, etc.) */
  content: string;
  /** Type of work product */
  type: string;
  /** MCP server type if applicable */
  serverType?: string;
  /** Buba's raw confidence before review (0-1) */
  rawConfidence: number;
  /** Self-assessed scores per dimension (optional) */
  selfAssessedScores?: Record<string, number>;
}

// ═══════════════════════════════════════════════════════════════
// LEARNED RULES LOADING
// ═══════════════════════════════════════════════════════════════

/**
 * Load learned rules from the feedback-patterns memory file.
 * Returns rules parsed from the "Learned Rules" section.
 */
function loadLearnedRules(memoryDir: string): CandidateRule[] {
  const filePath = path.join(memoryDir, "feedback-patterns.md");
  if (!fs.existsSync(filePath)) return [];

  // Return the default rules even if the file doesn't have parsed rules yet
  return getDefaultRules();
}

/** Default rules based on common patterns from the design spec */
function getDefaultRules(): CandidateRule[] {
  return [
    {
      rule: "No TypeScript `any` types",
      promptInstruction: "Never use TypeScript `any` — use `unknown` with type guards",
      confidence: 1.0,
      occurrences: 0,
      source: "feedback_pattern",
      domain: "code_quality",
      appliesTo: ["all"],
    },
    {
      rule: "All external API calls must have try/catch with typed errors",
      promptInstruction: "Every external API call must have try/catch with typed error classes",
      confidence: 0.95,
      occurrences: 0,
      source: "feedback_pattern",
      domain: "error_handling",
      appliesTo: ["all"],
    },
    {
      rule: "Include retry logic with exponential backoff on HTTP calls",
      promptInstruction: "Include retry logic with exponential backoff on all external HTTP calls",
      confidence: 0.9,
      occurrences: 0,
      source: "feedback_pattern",
      domain: "error_handling",
      appliesTo: ["all"],
    },
    {
      rule: "Validate inputs at function boundaries",
      promptInstruction: "Validate all user inputs at function boundaries using Zod or similar",
      confidence: 0.9,
      occurrences: 0,
      source: "feedback_pattern",
      domain: "code_quality",
      appliesTo: ["all"],
    },
    {
      rule: "No console.log for error reporting",
      promptInstruction: "Use proper error handling, not console.log for error reporting",
      confidence: 0.85,
      occurrences: 0,
      source: "feedback_pattern",
      domain: "error_handling",
      appliesTo: ["all"],
    },
    {
      rule: "No hardcoded configuration values",
      promptInstruction: "Use environment variables or config objects, not hardcoded values",
      confidence: 0.85,
      occurrences: 0,
      source: "feedback_pattern",
      domain: "code_quality",
      appliesTo: ["all"],
    },
  ];
}

// ═══════════════════════════════════════════════════════════════
// CONTENT CHECKERS
// ═══════════════════════════════════════════════════════════════

interface ContentCheck {
  name: string;
  description: string;
  severity: "nit" | "minor" | "major" | "critical";
  autoFixable: boolean;
  suggestion: string;
  check: (content: string) => boolean; // Returns true if violation found
}

const CONTENT_CHECKS: ContentCheck[] = [
  {
    name: "typescript-any",
    description: "TypeScript `any` type detected",
    severity: "major",
    autoFixable: false,
    suggestion: "Replace `any` with `unknown` and add type guards",
    check: (content) => /:\s*any\b|as\s+any\b|<any>/g.test(content),
  },
  {
    name: "console-log-error",
    description: "console.log used for error handling",
    severity: "minor",
    autoFixable: true,
    suggestion: "Use a proper logger or throw typed errors",
    check: (content) =>
      /console\.log\(.*(?:error|err|fail)/gi.test(content),
  },
  {
    name: "hardcoded-url",
    description: "Hardcoded URL or API endpoint detected",
    severity: "minor",
    autoFixable: true,
    suggestion: "Move URLs to configuration or environment variables",
    check: (content) =>
      /['"]https?:\/\/(?!localhost|127\.0\.0\.1|example\.com)[^'"]+['"]/g.test(
        content
      ),
  },
  {
    name: "missing-error-handling",
    description: "External API call without error handling",
    severity: "major",
    autoFixable: false,
    suggestion: "Wrap external calls in try/catch with typed errors",
    check: (content) => {
      // Rough heuristic: fetch/axios/http calls not inside try blocks
      const hasExternalCalls =
        /(?:fetch|axios|http\.|request)\(/g.test(content);
      const hasTryCatch = /try\s*{/g.test(content);
      return hasExternalCalls && !hasTryCatch;
    },
  },
  {
    name: "missing-input-validation",
    description: "Public function without input validation",
    severity: "minor",
    autoFixable: false,
    suggestion: "Add Zod schema validation at function boundaries",
    check: (content) => {
      // Heuristic: exported functions with parameters but no z.parse/safeParse
      const hasExports = /export\s+(?:async\s+)?function/g.test(content);
      const hasValidation = /\.(?:parse|safeParse)\(/g.test(content);
      return hasExports && !hasValidation;
    },
  },
  {
    name: "missing-jsdoc",
    description: "Exported function missing JSDoc comment",
    severity: "nit",
    autoFixable: true,
    suggestion: "Add JSDoc with @param, @returns, @throws",
    check: (content) => {
      const exportFunctions = content.match(
        /export\s+(?:async\s+)?function\s+\w+/g
      );
      if (!exportFunctions) return false;
      // Check if any export function is NOT preceded by a JSDoc comment
      for (const func of exportFunctions) {
        const idx = content.indexOf(func);
        const before = content.slice(Math.max(0, idx - 200), idx);
        if (!before.includes("*/")) return true;
      }
      return false;
    },
  },
  {
    name: "raw-string-throw",
    description: "Throwing raw strings instead of Error objects",
    severity: "major",
    autoFixable: true,
    suggestion: "Use `throw new Error(...)` or typed error classes",
    check: (content) => /throw\s+['"`]/g.test(content),
  },
];

// ═══════════════════════════════════════════════════════════════
// MAIN SELF-CHECK FUNCTION
// ═══════════════════════════════════════════════════════════════

/**
 * Run a self-check on a work product before sending to Jake.
 *
 * @param workProduct - The work product to check
 * @param memoryDir - Path to memory files for loading learned rules
 * @param calibrationCurve - Optional calibration curve for confidence adjustment
 * @returns SelfCheckResult with violations, predicted score, and recommendations
 */
export function selfCheck(
  workProduct: WorkProductForCheck,
  memoryDir: string,
  calibrationCurve?: CalibrationCurve
): SelfCheckResult {
  const violations: SelfCheckViolation[] = [];

  // 1. Run content checks
  for (const check of CONTENT_CHECKS) {
    if (check.check(workProduct.content)) {
      violations.push({
        rule: check.name,
        description: check.description,
        severity: check.severity,
        autoFixable: check.autoFixable,
        suggestion: check.suggestion,
      });
    }
  }

  // 2. Load and check against learned rules
  const learnedRules = loadLearnedRules(memoryDir);
  for (const rule of learnedRules) {
    // Check if this rule applies to this server type
    if (
      !rule.appliesTo.includes("all") &&
      workProduct.serverType &&
      !rule.appliesTo.includes(workProduct.serverType)
    ) {
      continue;
    }

    // For learned rules, we check if the content pattern appears
    // This is a simplified check — in production, this would use more sophisticated matching
    const ruleKeywords = rule.rule
      .toLowerCase()
      .split(/\s+/)
      .filter((w) => w.length > 3);
    const contentLower = workProduct.content.toLowerCase();
    const matchCount = ruleKeywords.filter((kw) =>
      contentLower.includes(kw)
    ).length;

    // If the rule mentions something that should be present but isn't
    if (
      rule.rule.toLowerCase().includes("always") &&
      matchCount < ruleKeywords.length * 0.3
    ) {
      // Low match might mean the required pattern is missing
      // This is heuristic — more sophisticated checking in production
    }
  }

  // 3. Predict score
  const majorViolations = violations.filter(
    (v) => v.severity === "major" || v.severity === "critical"
  ).length;
  const minorViolations = violations.filter(
    (v) => v.severity === "minor"
  ).length;

  // Base score from self-assessed scores or default
  let predictedScore = 7.5;
  if (workProduct.selfAssessedScores) {
    const scores = Object.values(workProduct.selfAssessedScores);
    if (scores.length > 0) {
      predictedScore =
        scores.reduce((sum, s) => sum + s, 0) / scores.length;
    }
  }

  // Adjust based on violations
  predictedScore -= majorViolations * 1.5;
  predictedScore -= minorViolations * 0.5;
  predictedScore = Math.max(1, Math.min(10, predictedScore));

  // 4. Apply calibration
  let confidence = workProduct.rawConfidence;
  if (calibrationCurve) {
    confidence = applyCalibration(confidence, calibrationCurve);
  }

  // Reduce confidence based on violations
  confidence -= majorViolations * 0.15;
  confidence -= minorViolations * 0.05;
  confidence = Math.max(0, Math.min(1, confidence));

  // 5. Determine pass/fail
  const passed = violations.filter(
    (v) => v.severity === "major" || v.severity === "critical"
  ).length === 0;

  return {
    passed,
    score: Math.round(predictedScore * 10) / 10,
    confidence: Math.round(confidence * 100) / 100,
    violations,
    recommendations: violations
      .filter((v) => v.autoFixable)
      .map((v) => v.suggestion),
  };
}
