import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { Logger } from '../utils/logger.js';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as yaml from 'js-yaml';
import { getCredential } from '../utils/keyring.js';
import { validateLudusRangeSchema } from './rangeConfig.js';

export interface InsertCredsRangeConfigArgs {
  configPath?: string;
  credentialMappings?: { [placeholder: string]: string };
  outputPath?: string;
  user?: string;
  help?: boolean;
}

// Base directory for all Ludus MCP operations
const LUDUS_MCP_BASE_DIR = path.join(os.homedir(), '.ludus-mcp');
const RANGE_CONFIG_TEMPLATES_DIR = path.join(LUDUS_MCP_BASE_DIR, 'range-config-templates');

/**
 * Resolve a file path for range configurations with smart organization
 */
function resolveConfigPath(filePath: string, user?: string): string {
  if (path.isAbsolute(filePath)) {
    return filePath;
  }
  
  // If it's just a filename (no directory separators)
  if (path.dirname(filePath) === '.') {
    if (user) {
      // User specified: put in user-specific subdirectory under range-config-templates
      const userTemplatesDir = path.join(RANGE_CONFIG_TEMPLATES_DIR, user);
      return path.join(userTemplatesDir, filePath);
    } else {
      // No user specified: put in base range-config-templates directory
      return path.join(RANGE_CONFIG_TEMPLATES_DIR, filePath);
    }
  }
  
  // If it's a relative path with directories, resolve relative to range-config-templates/
  return path.resolve(RANGE_CONFIG_TEMPLATES_DIR, filePath);
}

/**
 * Securely inject credentials into range config
 */
async function injectCredentialsIntoConfig(
  configContent: string,
  credentialMappings: { [placeholder: string]: string },
  logger: Logger
): Promise<{ success: boolean; content?: string; errors?: string[]; missingCreds?: string[] }> {
  let processedContent = configContent;
  const errors: string[] = [];
  const missingCreds: string[] = [];
  
  logger.info('Starting credential injection', { 
    placeholderCount: Object.keys(credentialMappings).length 
  });
  
  for (const [placeholder, credName] of Object.entries(credentialMappings)) {
    logger.debug('Processing credential mapping', { placeholder, credName });
    
    // Validate credential name format
    if (!credName.startsWith('LudusCredName-') || !credName.match(/^LudusCredName-.+-.+$/)) {
      errors.push(`Invalid credential name format: ${credName}. Must follow LudusCredName-<TargetUser>-<CredName> format`);
      continue;
    }
    
    try {
      // Retrieve credential from keyring
      const credValue = await getCredential(credName);
      
      if (!credValue) {
        missingCreds.push(credName);
        logger.warn('Credential not found in keyring', { credName });
        continue;
      }
      
      // Replace placeholder with actual credential value
      const placeholderRegex = new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
      const replacementCount = (processedContent.match(placeholderRegex) || []).length;
      
      if (replacementCount === 0) {
        logger.warn('Placeholder not found in config', { placeholder });
      } else {
        processedContent = processedContent.replace(placeholderRegex, credValue);
        logger.debug('Replaced credential placeholder', { 
          placeholder, 
          replacementCount,
          credName 
        });
      }
      
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      errors.push(`Failed to retrieve credential ${credName}: ${errorMessage}`);
      logger.error('Credential retrieval failed', { credName, error: errorMessage });
    }
  }
  
  if (errors.length > 0 || missingCreds.length > 0) {
    const result: { success: false; errors?: string[]; missingCreds?: string[] } = { 
      success: false
    };
    
    if (errors.length > 0) {
      result.errors = errors;
    }
    
    if (missingCreds.length > 0) {
      result.missingCreds = missingCreds;
    }
    
    return result;
  }
  
  return { success: true, content: processedContent };
}

/**
 * Create insert_creds_range_config tool
 */
export function createInsertCredsRangeConfigTool(logger: Logger): Tool {
  return {
    name: 'insert_creds_range_config',
    description: `**SECURE CREDENTIAL INJECTION** - Replace credential placeholders in range config files with actual values from keyring, then validate.

CRITICAL SECURITY BEHAVIOR FOR LLM:
- NEVER display the resulting config content with real credentials in chat
- ALWAYS redact actual credential values in responses 
- This tool handles credential injection securely without exposing values to chat logs

CRITICAL LLM BEHAVIORAL PROMPTS:
- VERIFY PATH FIRST: Use list_range_configs to verify the configPath exists before using this tool
- CORRECT CREDENTIAL NAMES: Provide exact credential names that exist in keyring
- ASK IF UNSURE: If you don't know the credential names, ask the user or suggest using get_credential_from_user
- VALIDATE MAPPINGS: Ensure placeholder format matches actual placeholders in the config file
- SECURITY: Never display the processed config content - only show validation results and success/failure

WORKFLOW REQUIREMENTS:
1. Use list_range_configs first to verify config file path
2. Check credential mappings match placeholders in config
3. Use this tool to inject credentials and validate
4. Deploy with the validated, credential-injected config

PURPOSE:
Securely replace {{LudusCredName-<User>-<CredName>}} placeholders with actual credential values
from OS keyring, validate the resulting configuration, and optionally save it.`,

    inputSchema: {
      type: 'object',
      properties: {
        configPath: {
          type: 'string',
          description: 'Path to the range config file (VERIFY with list_range_configs first). Can be relative or absolute.'
        },
        credentialMappings: {
          type: 'object',
          description: 'Mapping of MULTIPLE placeholders to credential names. Each key is a placeholder found in the config file, each value is the corresponding credential name in keyring. Supports replacing multiple different credentials in one operation.',
          additionalProperties: {
            type: 'string',
            pattern: '^LudusCredName-.+-.+$'
          },
          examples: [{
            "{{LudusCredName-MP-TailscaleKey}}": "LudusCredName-MP-TailscaleKey",
            "{{LudusCredName-MP-TailscaleAPIKey}}": "LudusCredName-MP-TailscaleAPIKey",
            "{{LudusCredName-TestRange-DatabasePassword}}": "LudusCredName-TestRange-DatabasePassword",
            "{{LudusCredName-Admin-ServiceToken}}": "LudusCredName-Admin-ServiceToken"
          }]
        },
        outputPath: {
          type: 'string',
          description: 'Optional: Where to save the credential-injected config. If not provided, creates temporary file for validation only.'
        },
        validateOnly: {
          type: 'boolean',
          description: 'If true (default), only validates the credential-injected config without saving. If false, saves to outputPath.',
          default: true
        },
        user: {
          type: 'string',
          description: 'User context for path resolution (optional). Used when configPath is relative.'
        }
      },
      required: ['configPath', 'credentialMappings']
    }
  };
}

/**
 * Handle insert_creds_range_config tool calls
 */
export async function handleInsertCredsRangeConfig(
  args: InsertCredsRangeConfigArgs,
  logger: Logger
): Promise<any> {
  const { configPath, credentialMappings, outputPath, user, help } = args;

  // Handle help-only mode
  if (help) {
    return {
      success: true,
      help: true,
      tool: 'insert_creds_range_config',
      description: 'Replace credential placeholders in range config with actual values from keyring',
      usage: {
        purpose: 'Securely inject credentials into Ludus range configuration files',
        workflow: [
          '1. Use list_range_configs to verify configPath exists',
          '2. Identify placeholders in config (e.g., {{LudusCredName-MP-TailscaleKey}})', 
          '3. Map each placeholder to its credential name in credentialMappings',
          '4. Credentials must exist in keyring (use get_credential_from_user first if needed)',
          '5. Tool validates and optionally saves the processed config'
        ],
        requiredParameters: {
          configPath: 'Path to range configuration file (relative to ~/.ludus-mcp/range-config-templates/)',
          credentialMappings: 'Object mapping placeholder strings to credential names from keyring'
        },
        optionalParameters: {
          outputPath: 'Where to save processed config (if omitted, overwrites original file)',
          user: 'User context for path resolution'
        },
        exampleUsage: {
          configPath: 'MP/simple-ad-tailscale-lab.yml',
          credentialMappings: {
            '{{LudusCredName-MP-TailscaleKey}}': 'LudusCredName-MP-TailscaleKey',
            '{{LudusCredName-MP-AdminPass}}': 'LudusCredName-MP-AdminPass'
          }
        },
        placeholderFormat: 'Placeholders must be in format: {{LudusCredName-<User>-<CredName>}}',
        credentialFormat: 'Credential names must follow: LudusCredName-<TargetUser>-<CredName>',
        pathResolution: {
          relativePaths: 'Resolved relative to ~/.ludus-mcp/range-config-templates/',
          absolutePaths: 'Used as-is',
          userSpecific: 'If user provided, creates user-specific subdirectory'
        },
        security: [
          'Actual credential values are never exposed in responses',
          'Credentials are retrieved securely from OS keyring',
          'Processed configs are validated against Ludus schema'
        ]
      }
    };
  }

  // Validate required parameters when not in help mode
  if (!configPath || !credentialMappings) {
    return {
      success: false,
      error: 'Missing required parameters',
      message: 'configPath and credentialMappings are required. Use help: true for usage information.',
      requiredParameters: ['configPath', 'credentialMappings']
    };
  }

  logger.info('Handling insert_creds_range_config request', {
    configPath,
    credentialCount: Object.keys(credentialMappings).length,
    user
  });

  try {
    // Resolve and validate config path
    const resolvedConfigPath = resolveConfigPath(configPath, user);
    
    if (!fs.existsSync(resolvedConfigPath)) {
      return {
        success: false,
        error: 'Config file not found',
        configPath: resolvedConfigPath,
        originalPath: configPath,
        message: `Configuration file not found: ${resolvedConfigPath}`,
        suggestion: 'Use list_range_configs to verify the correct file path'
      };
    }

    // Read config file
    const configContent = fs.readFileSync(resolvedConfigPath, 'utf8');
    
    // Validate credential mappings
    const validationErrors: string[] = [];
    for (const [placeholder, credName] of Object.entries(credentialMappings)) {
      if (!configContent.includes(placeholder)) {
        validationErrors.push(`Placeholder "${placeholder}" not found in config file`);
      }
      if (!credName.match(/^LudusCredName-.+-.+$/)) {
        validationErrors.push(`Invalid credential name format: "${credName}"`);
      }
    }
    
    if (validationErrors.length > 0) {
      return {
        success: false,
        error: 'Validation failed',
        validationErrors,
        configPath: resolvedConfigPath,
        message: 'Credential mapping validation failed'
      };
    }

    // Inject credentials
    const injectionResult = await injectCredentialsIntoConfig(
      configContent,
      credentialMappings,
      logger
    );
    
    if (!injectionResult.success) {
      return {
        success: false,
        error: 'Credential injection failed',
        errors: injectionResult.errors,
        missingCredentials: injectionResult.missingCreds,
        configPath: resolvedConfigPath,
        message: 'Failed to inject credentials into config',
        suggestions: injectionResult.missingCreds?.length ? [
          'Missing credentials need to be stored first using get_credential_from_user',
          'Verify credential names match exactly what was stored',
          'Check OS credential manager for stored credentials'
        ] : undefined
      };
    }

    // Validate the credential-injected YAML
    let parsedConfig;
    try {
      parsedConfig = yaml.load(injectionResult.content!);
    } catch (error) {
      return {
        success: false,
        error: 'YAML parsing failed after credential injection',
        yamlError: error instanceof Error ? error.message : String(error),
        configPath: resolvedConfigPath,
        message: 'The credential-injected config has invalid YAML syntax'
      };
    }

    // Validate against Ludus schema
    const schemaValidation = await validateLudusRangeSchema(parsedConfig, logger);
    
    // Handle output - always save the credential-injected config
    const targetPath = outputPath ? resolveConfigPath(outputPath, user) : resolvedConfigPath;
    const outputDir = path.dirname(targetPath);
    
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }
    
    // Write the credential-injected config
    fs.writeFileSync(targetPath, injectionResult.content!, 'utf8');
    const finalOutputPath = targetPath;
    
    logger.info('Credential-injected config saved', { 
      outputPath: targetPath,
      overwroteOriginal: !outputPath
    });

    // Prepare response (with credential redaction)
    const response = {
      success: true,
      configPath: resolvedConfigPath,
      originalPath: configPath,
      credentialsInjected: Object.keys(credentialMappings).length,
      validation: schemaValidation,
      outputPath: finalOutputPath,
      message: `Credential injection completed and config saved to ${finalOutputPath}`,
      overwroteOriginal: !outputPath,
      
      // Security: Never expose the actual credential-injected content
      securityNote: 'Actual credential values have been redacted from this response for security',
      
      nextSteps: [
        'Credentials have been injected and file saved',
        'Use set_range_config to make this the active configuration',
        'Use deploy_range to deploy with real credentials'
      ]
    };

    return response;

  } catch (error: any) {
    logger.error('insert_creds_range_config failed', {
      configPath,
      error: error.message
    });

    return {
      success: false,
      error: error.message,
      configPath,
      message: `Failed to process credential injection: ${error.message}`
    };
  }
} 