/**
 * Stop Hook Executor — L-Thread validation loops
 *
 * Manages stop hooks for Long-Running threads.
 * Executes hook scripts, captures output, provides structured feedback.
 *
 * On failure: writes feedback to .morph/stop-hook-feedback/{threadId}.json
 * Agent reads feedback before retrying.
 *
 * Max retries: 5. Interval: configurable (default: 30 min).
 */

import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { execSync } from 'child_process';
import { recordEvent } from '../analytics/analytics-engine.js';

const FEEDBACK_DIR = join(process.cwd(), '.morph/stop-hook-feedback');
const MAX_RETRIES = 5;
const DEFAULT_INTERVAL_MINUTES = 30;

// Hook registry — hooks registered for each thread
const hookRegistry = new Map(); // threadId → [{ hookPath, config }]

// ============================================================================
// Hook Registration
// ============================================================================

/**
 * Register a stop hook for a thread
 * @param {string} threadId - Thread ID
 * @param {string} hookPath - Path to hook script
 * @param {Object} [config]
 * @param {string[]} [config.triggers] - When to run: ['interval', 'on-stop', 'on-error']
 * @param {number} [config.intervalMinutes] - Interval for periodic execution
 * @param {boolean} [config.blocking] - Whether failure blocks thread progress
 */
export function registerStopHook(threadId, hookPath, config = {}) {
  if (!hookRegistry.has(threadId)) {
    hookRegistry.set(threadId, []);
  }

  hookRegistry.get(threadId).push({
    hookPath,
    config: {
      triggers: config.triggers || ['on-stop'],
      intervalMinutes: config.intervalMinutes || DEFAULT_INTERVAL_MINUTES,
      blocking: config.blocking !== false,
      ...config
    }
  });
}

/**
 * Get registered hooks for a thread
 * @param {string} threadId
 * @returns {Array}
 */
export function getThreadHooks(threadId) {
  return hookRegistry.get(threadId) || [];
}

// ============================================================================
// Hook Execution
// ============================================================================

/**
 * Execute all stop hooks for a thread
 * @param {string} threadId - Thread ID
 * @param {string} hookType - Trigger type ('interval' | 'on-stop' | 'on-error')
 * @param {Object} [context] - Additional context passed to hooks
 * @returns {Promise<Object>} { passed, feedback, results }
 */
export async function executeStopHooks(threadId, hookType = 'on-stop', context = {}) {
  const hooks = hookRegistry.get(threadId) || loadDefaultHooks(threadId);
  const results = [];
  let allPassed = true;

  for (const hook of hooks) {
    if (!hook.config.triggers.includes(hookType) && !hook.config.triggers.includes('all')) {
      continue;
    }

    const result = await executeHook(hook.hookPath, { threadId, hookType, ...context });
    results.push(result);

    if (!result.passed && hook.config.blocking) {
      allPassed = false;
    }
  }

  const passed = allPassed;
  const feedback = buildFeedback(results, passed);

  if (!passed) {
    saveFeedback(threadId, feedback);
  }

  recordEvent({
    type: passed ? 'stop_hook_passed' : 'stop_hook_failed',
    data: { threadId, hookType, resultsCount: results.length, passed }
  });

  return { passed, feedback, results };
}

/**
 * Execute a single hook script
 * @param {string} hookPath - Path to hook script
 * @param {Object} context - Context passed as environment variables
 * @returns {Promise<Object>} { hookPath, passed, output, error }
 */
async function executeHook(hookPath, context) {
  const fullPath = existsSync(hookPath) ? hookPath : join(process.cwd(), hookPath);

  if (!existsSync(fullPath)) {
    return {
      hookPath,
      passed: true, // Missing hooks don't block
      output: null,
      error: `Hook not found: ${hookPath} (skipped)`,
      skipped: true
    };
  }

  try {
    const env = {
      ...process.env,
      MORPH_THREAD_ID: context.threadId || '',
      MORPH_HOOK_TYPE: context.hookType || '',
      MORPH_FEATURE: context.feature || ''
    };

    const output = execSync(`node "${fullPath}"`, {
      encoding: 'utf8',
      stdio: 'pipe',
      timeout: 120000, // 2 minute timeout per hook
      env
    });

    // Parse JSON output from hook
    let parsed;
    try {
      parsed = JSON.parse(output);
    } catch {
      // Non-JSON output means pass (for legacy hooks)
      parsed = { passed: true, output };
    }

    return {
      hookPath,
      passed: parsed.passed !== false,
      output: parsed,
      error: parsed.error || null
    };
  } catch (err) {
    return {
      hookPath,
      passed: false,
      output: null,
      error: err.message || 'Hook execution failed'
    };
  }
}

// ============================================================================
// Feedback Management
// ============================================================================

/**
 * Build structured feedback from hook results
 * @param {Array} results - Hook execution results
 * @param {boolean} passed - Overall pass/fail
 * @returns {Object} Structured feedback
 */
function buildFeedback(results, passed) {
  const failures = results.filter(r => !r.passed && !r.skipped);
  const issues = failures.flatMap(r => {
    const output = r.output;
    if (output?.issues) return output.issues;
    if (output?.errors) return output.errors;
    if (r.error) return [{ message: r.error, severity: 'error' }];
    return [];
  });

  return {
    passed,
    timestamp: new Date().toISOString(),
    hooksRun: results.length,
    hooksFailed: failures.length,
    issues,
    suggestions: failures.flatMap(r => r.output?.suggestions || []),
    nextAction: passed
      ? 'continue'
      : failures.length > 0 ? 'fix-and-retry' : 'investigate'
  };
}

/**
 * Save feedback to file for agent to read
 * @param {string} threadId - Thread ID
 * @param {Object} feedback - Feedback object
 * @returns {string} Path to feedback file
 */
export function saveFeedback(threadId, feedback) {
  if (!existsSync(FEEDBACK_DIR)) {
    mkdirSync(FEEDBACK_DIR, { recursive: true });
  }

  const filePath = join(FEEDBACK_DIR, `${threadId}.json`);
  writeFileSync(filePath, JSON.stringify(feedback, null, 2), 'utf8');
  return filePath;
}

/**
 * Load feedback for a thread (agent reads this after failure)
 * @param {string} threadId - Thread ID
 * @returns {Object|null} Feedback object or null
 */
export function loadFeedback(threadId) {
  const filePath = join(FEEDBACK_DIR, `${threadId}.json`);
  if (!existsSync(filePath)) return null;
  try {
    return JSON.parse(readFileSync(filePath, 'utf8'));
  } catch {
    return null;
  }
}

/**
 * Track retry count for a thread
 * @param {string} threadId
 * @returns {number} Current retry count
 */
export function getRetryCount(threadId) {
  const feedback = loadFeedback(threadId);
  return feedback?.retryCount || 0;
}

/**
 * Increment retry count
 * @param {string} threadId
 * @returns {number} New retry count
 */
export function incrementRetryCount(threadId) {
  const feedback = loadFeedback(threadId) || {};
  feedback.retryCount = (feedback.retryCount || 0) + 1;
  saveFeedback(threadId, feedback);
  return feedback.retryCount;
}

/**
 * Check if max retries exceeded
 * @param {string} threadId
 * @param {number} [maxRetries=5]
 * @returns {boolean}
 */
export function isMaxRetriesExceeded(threadId, maxRetries = MAX_RETRIES) {
  return getRetryCount(threadId) >= maxRetries;
}

// ============================================================================
// Helpers
// ============================================================================

function loadDefaultHooks(threadId) {
  // Load hooks from llm-interaction.json if no hooks registered
  const configPath = join(process.cwd(), '.morph/config/llm-interaction.json');
  if (!existsSync(configPath)) return [];

  try {
    const config = JSON.parse(readFileSync(configPath, 'utf8'));
    const hookNames = config.stopHooks?.hooks || [];

    return hookNames.map(name => ({
      hookPath: `framework/hooks/agent-stop/${name}.js`,
      config: {
        triggers: ['on-stop', 'interval'],
        intervalMinutes: config.stopHooks?.interval || DEFAULT_INTERVAL_MINUTES,
        blocking: true
      }
    }));
  } catch {
    return [];
  }
}
