/**
 * Schema Validation Utility
 * Implements MCP Design Guide Section 2.3 principles for robust tool input validation
 */

import { ErrorHandler } from './error-handler.js';

export interface ValidationRule {
  field: string;
  type: string;
  required?: boolean;
  min?: number;
  max?: number;
  pattern?: string;
  enum?: any[];
  custom?: (value: any) => boolean | string;
}

export interface SchemaDefinition {
  type: 'object';
  properties: Record<string, any>;
  required?: string[];
  additionalProperties?: boolean;
}

export class SchemaValidator {
  /**
   * Enhanced schema patterns for common MCP tool parameters
   */
  static readonly COMMON_SCHEMAS = {
    // Identifiers
    ID: {
      type: 'string',
      pattern: '^[a-zA-Z0-9\\-_]+$',
      minLength: 1,
      maxLength: 50,
      description: 'Alphanumeric identifier with hyphens and underscores'
    },

    UUID: {
      type: 'string',
      pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
      description: 'UUID v4 format'
    },

    // Names and titles
    NAME: {
      type: 'string',
      minLength: 1,
      maxLength: 100,
      pattern: '^[a-zA-Z0-9\\s\\-_\\.]+$',
      description: 'Name with alphanumeric characters, spaces, hyphens, underscores, and periods'
    },

    TITLE: {
      type: 'string',
      minLength: 1,
      maxLength: 150,
      description: 'Title or summary text'
    },

    // Descriptions
    SHORT_DESCRIPTION: {
      type: 'string',
      minLength: 5,
      maxLength: 500,
      description: 'Short description text'
    },

    LONG_DESCRIPTION: {
      type: 'string',
      minLength: 10,
      maxLength: 2000,
      description: 'Detailed description text'
    },

    // Dates and times
    DATE: {
      type: 'string',
      format: 'date',
      description: 'Date in YYYY-MM-DD format'
    },

    DATETIME: {
      type: 'string',
      format: 'date-time',
      description: 'Date and time in ISO 8601 format'
    },

    // Priority levels
    PRIORITY: {
      type: 'string',
      enum: ['low', 'medium', 'high', 'critical'],
      description: 'Priority level'
    },

    // Status values
    AGILE_STATUS: {
      type: 'string',
      enum: ['planned', 'active', 'completed', 'cancelled'],
      description: 'Agile item status'
    },

    TASK_STATUS: {
      type: 'string',
      enum: ['todo', 'in_progress', 'review', 'testing', 'done', 'blocked'],
      description: 'Task status'
    },

    // Story points (Fibonacci sequence)
    STORY_POINTS: {
      type: 'number',
      enum: [0, 1, 2, 3, 5, 8, 13, 21],
      description: 'Story points using Fibonacci sequence'
    },

    // Duration constraints
    SPRINT_DURATION: {
      type: 'number',
      minimum: 1,
      maximum: 30,
      description: 'Sprint duration in days'
    },

    // Email format
    EMAIL: {
      type: 'string',
      format: 'email',
      description: 'Valid email address'
    },

    // URL format
    URL: {
      type: 'string',
      format: 'uri',
      description: 'Valid URL'
    },

    // Arrays with constraints
    TAG_ARRAY: {
      type: 'array',
      items: {
        type: 'string',
        minLength: 1,
        maxLength: 30,
        pattern: '^[a-zA-Z0-9\\-_]+$'
      },
      maxItems: 10,
      uniqueItems: true,
      description: 'Array of tags (max 10, alphanumeric with hyphens/underscores)'
    },

    STRING_ARRAY: {
      type: 'array',
      items: {
        type: 'string',
        minLength: 1,
        maxLength: 200
      },
      maxItems: 20,
      description: 'Array of strings (max 20 items)'
    }
  };

  /**
   * Create enhanced schema for tool definitions
   */
  static createToolSchema(fields: Record<string, string | object>, required: string[] = []): SchemaDefinition {
    const properties: Record<string, any> = {};

    for (const [fieldName, schemaType] of Object.entries(fields)) {
      if (typeof schemaType === 'string') {
        // Use predefined schema
        if (this.COMMON_SCHEMAS[schemaType as keyof typeof this.COMMON_SCHEMAS]) {
          properties[fieldName] = { ...this.COMMON_SCHEMAS[schemaType as keyof typeof this.COMMON_SCHEMAS] };
        } else {
          throw new Error(`Unknown schema type: ${schemaType}`);
        }
      } else {
        // Use custom schema object
        properties[fieldName] = schemaType;
      }
    }

    return {
      type: 'object',
      properties,
      required,
      additionalProperties: false
    };
  }

  /**
   * Validate parameters against schema with detailed error reporting
   */
  static validateParameters(
    params: any,
    schema: SchemaDefinition,
    context: { tool: string; module: string }
  ): void {
    if (!params || typeof params !== 'object') {
      throw ErrorHandler.createValidationError(
        'params',
        params,
        'must be an object',
        context
      );
    }

    // Check required fields
    if (schema.required) {
      for (const field of schema.required) {
        if (!(field in params) || params[field] === undefined || params[field] === null) {
          throw ErrorHandler.createValidationError(
            field,
            params[field],
            'is required',
            context
          );
        }
      }
    }

    // Validate each property
    for (const [fieldName, value] of Object.entries(params)) {
      if (value === undefined || value === null) {
        continue; // Skip undefined/null values for optional fields
      }

      const fieldSchema = schema.properties[fieldName];
      if (!fieldSchema && !schema.additionalProperties) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          'is not allowed (additional properties forbidden)',
          context
        );
      }

      if (fieldSchema) {
        this.validateField(fieldName, value, fieldSchema, context);
      }
    }
  }

  /**
   * Validate individual field against its schema
   */
  private static validateField(
    fieldName: string,
    value: any,
    schema: any,
    context: { tool: string; module: string }
  ): void {
    // Type validation
    if (schema.type) {
      if (!this.validateType(value, schema.type)) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must be of type ${schema.type}`,
          context
        );
      }
    }

    // String validations
    if (schema.type === 'string' && typeof value === 'string') {
      if (schema.minLength && value.length < schema.minLength) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must be at least ${schema.minLength} characters long`,
          context
        );
      }

      if (schema.maxLength && value.length > schema.maxLength) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must be no more than ${schema.maxLength} characters long`,
          context
        );
      }

      if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must match pattern: ${schema.pattern}`,
          context
        );
      }

      if (schema.format) {
        this.validateFormat(fieldName, value, schema.format, context);
      }
    }

    // Number validations
    if (schema.type === 'number' && typeof value === 'number') {
      if (schema.minimum !== undefined && value < schema.minimum) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must be at least ${schema.minimum}`,
          context
        );
      }

      if (schema.maximum !== undefined && value > schema.maximum) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must be no more than ${schema.maximum}`,
          context
        );
      }
    }

    // Array validations
    if (schema.type === 'array' && Array.isArray(value)) {
      if (schema.minItems && value.length < schema.minItems) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must have at least ${schema.minItems} items`,
          context
        );
      }

      if (schema.maxItems && value.length > schema.maxItems) {
        throw ErrorHandler.createValidationError(
          fieldName,
          value,
          `must have no more than ${schema.maxItems} items`,
          context
        );
      }

      if (schema.uniqueItems) {
        const uniqueValues = new Set(value);
        if (uniqueValues.size !== value.length) {
          throw ErrorHandler.createValidationError(
            fieldName,
            value,
            'must contain unique items only',
            context
          );
        }
      }

      // Validate each array item
      if (schema.items) {
        value.forEach((item, index) => {
          this.validateField(`${fieldName}[${index}]`, item, schema.items, context);
        });
      }
    }

    // Enum validation
    if (schema.enum && !schema.enum.includes(value)) {
      throw ErrorHandler.createValidationError(
        fieldName,
        value,
        `must be one of: ${schema.enum.join(', ')}`,
        context
      );
    }
  }

  /**
   * Validate data type
   */
  private static validateType(value: any, expectedType: string): boolean {
    switch (expectedType) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number' && !isNaN(value);
      case 'boolean':
        return typeof value === 'boolean';
      case 'array':
        return Array.isArray(value);
      case 'object':
        return typeof value === 'object' && value !== null && !Array.isArray(value);
      default:
        return true;
    }
  }

  /**
   * Validate string formats
   */
  private static validateFormat(
    fieldName: string,
    value: string,
    format: string,
    context: { tool: string; module: string }
  ): void {
    switch (format) {
      case 'email':
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(value)) {
          throw ErrorHandler.createValidationError(
            fieldName,
            value,
            'must be a valid email address',
            context
          );
        }
        break;

      case 'uri':
        try {
          new URL(value);
        } catch {
          throw ErrorHandler.createValidationError(
            fieldName,
            value,
            'must be a valid URL',
            context
          );
        }
        break;

      case 'date':
        const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
        if (!dateRegex.test(value) || isNaN(Date.parse(value))) {
          throw ErrorHandler.createValidationError(
            fieldName,
            value,
            'must be a valid date in YYYY-MM-DD format',
            context
          );
        }
        break;

      case 'date-time':
        if (isNaN(Date.parse(value))) {
          throw ErrorHandler.createValidationError(
            fieldName,
            value,
            'must be a valid ISO 8601 date-time',
            context
          );
        }
        break;
    }
  }

  /**
   * Create a decorator for automatic parameter validation
   */
  static validateParams(schema: SchemaDefinition) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      const originalMethod = descriptor.value;

      descriptor.value = async function (...args: any[]) {
        const params = args[0];
        const context = {
          tool: propertyKey,
          module: target.constructor.name,
        };

        SchemaValidator.validateParameters(params, schema, context);
        return await originalMethod.apply(this, args);
      };

      return descriptor;
    };
  }
}