import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import InifniBadRequestException from 'src/common/exceptions/infini-badrequest-exception';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
import { WorkflowConfigurationService } from './workflow-configuration.service';
import { WorkflowValidationService } from './workflow-validation.service';
import { AppLoggerService } from 'src/common/services/logger.service';

interface SectionPermissions {
  create?: PermissionRule[];
  read?: PermissionRule[];
  update?: PermissionRule[];
  delete?: PermissionRule[];
}

interface PermissionRule {
  roles: string[];
  condition: any;
}

interface SectionDefinition {
  sectionKey: string;
  permissions: SectionPermissions;
}

export type OperationType = 'create' | 'read' | 'update' | 'delete';

@Injectable()
export class WorkflowPermissionValidationService {
  constructor(
    private readonly dataSource: DataSource,
    private readonly workflowConfigurationService: WorkflowConfigurationService,
    private readonly workflowValidationService: WorkflowValidationService,
    private readonly logger: AppLoggerService,
  ) {}

  /**
   * Validate if a user with given roles can perform an operation on specified sections
   * @param registrationId - Registration ID (undefined for new registrations)
   * @param programTypeKey - Program type key (e.g., 'PT_HDBMSD')
   * @param stageKey - Workflow stage key (e.g., 'REGISTRATION')
   * @param userRoles - User's role keys (e.g., ['ROLE_VIEWER', 'ROLE_ADMIN'])
   * @param operation - Operation type ('create', 'update', 'read', 'delete')
   * @param sectionKeys - Section keys being accessed (e.g., ['FS_BASICDETAILS'])
   * @returns Promise<boolean> - True if allowed, throws exception if not
   */
  async validateSectionPermissions(
    registrationId: number | undefined,
    programTypeKey: string,
    stageKey: string,
    userRoles: string[],
    operation: OperationType,
    sectionKeys: string[],
    workflowId?: number
  ): Promise<boolean> {
    this.logger.log('Validating section permissions', {
      registrationId,
      programTypeKey,
      stageKey,
      userRoles,
      operation,
      sectionKeys,
      workflowId,
    });

    try {
      // Get workflow configuration by ID only
      if (!workflowId) {
        this.logger.error('workflowId is required for permission validation', undefined, { programTypeKey, registrationId });
        throw new InifniBadRequestException(
          ERROR_CODES.WORKFLOW_ID_MISSING_IN_PROGRAM,
          null,
          null,
          'workflowId is required for permission validation'
        );
      }

      const workflow = await this.workflowConfigurationService.findById(workflowId);

      if (!workflow) {
        this.logger.error('Workflow configuration not found for workflowId', undefined, { workflowId, programTypeKey });
        throw new InifniBadRequestException(
          ERROR_CODES.WORKFLOW_CONFIGURATION_NOTFOUND_BY_ID,
          null,
          null,
          `Workflow configuration not found for workflowId: ${workflowId}`
        );
      }

      // Find the target stage
      const stage = workflow.stages?.find((s: any) => s.stageKey === stageKey);

      if (!stage) {
        this.logger.warn('Stage not found in workflow configuration', { stageKey, workflowId });
        return true; // Allow if stage not found in config
      }

      // If stage validate the prerequisites for the stage, if any
      await this.workflowValidationService.validateStagePrerequisites(stage, userRoles, registrationId, workflowId);
      
      if (!stage) {
        this.logger.warn('Stage not found in workflow', { stageKey, programTypeKey, workflowId });
        return true; // Allow if stage not found
      }

      // Get workflow data for condition evaluation (skip for new registrations)
      const workflowData = registrationId 
        ? await this.workflowValidationService.getWorkflowDataForRegistration(registrationId, workflowId)
        : {}; // Empty object for new registrations

      // Get sections from executionRulesConfig
      const sections: SectionDefinition[] = Array.isArray(stage.executionRulesConfig?.sections) 
        ? stage.executionRulesConfig.sections 
        : [];

      if (sections.length === 0) {
        this.logger.warn('No sections defined in stage execution conditions', { stageKey });
        return true; // Allow if no sections defined
      }

      // Validate each section being accessed
      for (const sectionKey of sectionKeys) {
        const section = sections.find((s: SectionDefinition) => s.sectionKey === sectionKey);
        
        if (!section) {
          this.logger.warn('Section not found in stage configuration', { sectionKey, stageKey });
          continue; // Skip if section not in workflow config
        }

        // Check permissions for the operation
        const hasPermission = await this.checkOperationPermission(
          section,
          operation,
          userRoles,
          workflowData,
          registrationId,
          sectionKey
        );

        if (!hasPermission) {
          throw new InifniBadRequestException(
            ERROR_CODES.WORKFLOW_PERMISSION_DENIED,
            null,
            null,
            `User does not have permission to ${operation} section ${sectionKey}`,
            userRoles.join(', ')
          );
        }
      }

      return true;
    } catch (error) {
      if (error instanceof InifniBadRequestException) {
        throw error;
      }
      this.logger.error('Error validating section permissions', error?.stack, {
        registrationId,
        programTypeKey,
        stageKey,
        operation,
        sectionKeys,
      });
      throw error;
    }
  }

  /**
   * Check if user has permission for a specific operation on a section
   */
  private async checkOperationPermission(
    section: SectionDefinition,
    operation: OperationType,
    userRoles: string[],
    workflowData: Record<string, unknown>,
    registrationId: number | undefined,
    sectionKey: string,
  ): Promise<boolean> {
    const permissionRules = section.permissions?.[operation];
    
    this.logger.log('Checking permissions for operation', {
      operation,
      sectionKey,
      userRoles,
      permissionRules,
      workflowDataKeys: Object.keys(workflowData),
      registrationId,
    });
    
    if (!permissionRules || permissionRules.length === 0) {
      this.logger.warn('No permission rules defined for operation', {
        operation,
        sectionKey,
        registrationId,
      });
      return false; // Deny if no rules defined
    }

    // Check if any permission rule matches
    for (const rule of permissionRules) {
      const hasRole = this.checkUserHasRole(userRoles, rule.roles);
      
      this.logger.log('Checking role match', {
        userRoles,
        requiredRoles: rule.roles,
        hasRole,
        sectionKey,
      });
      
      if (!hasRole) {
        continue; // Skip if user doesn't have required role
      }

      // If role matches and no condition, allow
      if (!rule.condition || rule.condition === null) {
        this.logger.log('Permission granted - role matched, no condition', {
          operation,
          sectionKey,
          userRoles,
          ruleRoles: rule.roles,
        });
        return true;
      }

      // Evaluate condition
      this.logger.log('Evaluating condition for matched role', {
        condition: rule.condition,
        workflowDataKeys: Object.keys(workflowData),
        sectionKey,
      });
      
      const conditionMet = this.evaluatePermissionCondition(rule.condition, workflowData);
      
      this.logger.log('Condition evaluation result', {
        conditionMet,
        condition: rule.condition,
        workflowDataKeys: Object.keys(workflowData),
        sectionKey,
      });
      
      if (conditionMet) {
        this.logger.log('Permission granted - role and condition matched', {
          operation,
          sectionKey,
          userRoles,
          condition: rule.condition,
        });
        return true;
      }
    }

    this.logger.warn('Permission denied - no matching rules', {
      operation,
      sectionKey,
      userRoles,
      availableRules: permissionRules,
      workflowData,
    });

    return false;
  }

  /**
   * Check if user has any of the required roles
   */
  private checkUserHasRole(userRoles: string[], requiredRoles: string[]): boolean {
    // Check for 'ALL' special role
    if (requiredRoles.includes('ALL')) {
      return true;
    }

    // Check if user has any of the required roles
    return requiredRoles.some(requiredRole => 
      userRoles.some(userRole => 
        userRole.toUpperCase() === requiredRole.toUpperCase()
      )
    );
  }

  /**
   * Evaluate permission condition against workflow data
   */
  private evaluatePermissionCondition(
    condition: any,
    workflowData: Record<string, unknown>,
  ): boolean {
    if (!condition) {
      return true;
    }

    this.logger.log('Evaluating permission condition', { 
      condition, 
      workflowDataKeys: Object.keys(workflowData),
      conditionType: condition.operator 
    });

    // Handle AND operator
    if (condition.operator === 'AND' && condition.conditions) {
      const results = condition.conditions.map((subCondition: any) => {
        const result = this.evaluatePermissionCondition(subCondition, workflowData);
        this.logger.log('AND sub-condition result', { subCondition, result });
        return result;
      });
      const finalResult = results.every(r => r);
      this.logger.log('AND condition final result', { finalResult, results });
      return finalResult;
    }

    // Handle OR operator
    if (condition.operator === 'OR' && condition.conditions) {
      const results = condition.conditions.map((subCondition: any) => {
        const result = this.evaluatePermissionCondition(subCondition, workflowData);
        this.logger.log('OR sub-condition result', { subCondition, result });
        return result;
      });
      const finalResult = results.some(r => r);
      this.logger.log('OR condition final result', { finalResult, results });
      return finalResult;
    }

    // Handle simple field conditions
    if (condition.field && condition.operator) {
      const fieldValue = workflowData[condition.field];
      const result = this.evaluateCondition(fieldValue, condition.operator, condition.value);
      this.logger.log('Field condition evaluation', {
        field: condition.field,
        fieldValue,
        operator: condition.operator,
        expectedValue: condition.value,
        result,
      });
      return result;
    }

    this.logger.warn('Unknown condition structure', { condition });
    return false;
  }

  /**
   * Evaluate a single condition
   */
  private evaluateCondition(fieldValue: unknown, operator: string, value: unknown): boolean {
    this.logger.log('Evaluating single condition', {
      fieldValue,
      fieldValueType: typeof fieldValue,
      operator,
      expectedValue: value,
      expectedValueType: typeof value,
    });

    switch (operator.toUpperCase()) {
      case 'EQUALS': {
        const equalsResult = Array.isArray(value) ? value.includes(fieldValue as never) : fieldValue === value;
        this.logger.log('EQUALS comparison', { fieldValue, value, result: equalsResult });
        return equalsResult;
      }
      case 'NOT_EQUALS':
        return Array.isArray(value) ? !value.includes(fieldValue as never) : fieldValue !== value;
      
      case 'IN':
        return Array.isArray(value) && value.includes(fieldValue as never);
      
      case 'NOT_IN':
        return Array.isArray(value) && !value.includes(fieldValue as never);
      
      case 'IS_NULL':
        return fieldValue === null || fieldValue === undefined;
      
      case 'IS_NOT_NULL':
        return fieldValue !== null && fieldValue !== undefined;
      
      case 'GREATER_THAN':
        return typeof fieldValue === 'number' && typeof value === 'number' && fieldValue > value;
      
      case 'LESS_THAN':
        return typeof fieldValue === 'number' && typeof value === 'number' && fieldValue < value;
      
      case 'GREATER_THAN_OR_EQUALS':
        return typeof fieldValue === 'number' && typeof value === 'number' && fieldValue >= value;
      
      case 'LESS_THAN_OR_EQUALS':
        return typeof fieldValue === 'number' && typeof value === 'number' && fieldValue <= value;
      
      default:
        this.logger.warn('Unknown operator in condition evaluation', { operator });
        return false;
    }
  }

  /**
   * Determine which sections are being accessed based on answers
   * @param answers - Registration answers
   * @param programId - Program ID
   * @returns Promise<string[]> - Array of section keys
   */
  async determineSectionsFromAnswers(
    answers: any[],
    programId: number,
  ): Promise<string[]> {
    try {
      if (!answers || answers.length === 0) {
        return [];
      }

      const questionIds = answers.map((a: any) => a.questionId).filter(Boolean);
      
      if (questionIds.length === 0) {
        return [];
      }

      // Query program questions to get form sections
      const programQuestions = await this.dataSource
        .getRepository('ProgramQuestion')
        .createQueryBuilder('pq')
        .leftJoinAndSelect('pq.programQuestionFormSection', 'fs')
        .where('pq.question_id IN (:...questionIds)', { questionIds })
        .andWhere('pq.program_id = :programId', { programId })
        .andWhere('pq.deletedAt IS NULL')
        .getMany();

      const formSectionKeys = programQuestions
        .map((pq: any) => pq.programQuestionFormSection?.key)
        .filter(Boolean);

      // Return unique section keys
      return Array.from(new Set(formSectionKeys));
    } catch (error) {
      this.logger.error('Error determining sections from answers', error?.stack, {
        programId,
        answersCount: answers?.length,
      });
      return [];
    }
  }

  /**
   * Validate sections and conditions for a given stage transition
   * @param registrationId - Registration ID (undefined for new registrations)
   * @param programTypeKey - Program type key
   * @param targetStageKey - Target stage key
   * @param sectionKeys - Sections being submitted
   * @param userRoles - User roles
   * @param operation - Operation type
   */
  async validateStageTransitionPermissions(
    registrationId: number | undefined,
    programTypeKey: string,
    targetStageKey: string,
    sectionKeys: string[],
    userRoles: string[],
    operation: OperationType,
    workflowId?: number
  ): Promise<boolean> {
    this.logger.log('Validating stage transition permissions', {
      registrationId,
      programTypeKey,
      targetStageKey,
      sectionKeys,
      userRoles,
      operation,
    });

    return await this.validateSectionPermissions(
      registrationId,
      programTypeKey,
      targetStageKey,
      userRoles,
      operation,
      sectionKeys,
      workflowId
    );
  }
}
