import { Injectable, Inject, forwardRef } 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 { WorkflowStage } from 'src/common/entities';
import { WorkflowConfigurationService } from './workflow-configuration.service';
import { WorkflowVariableService } from './workflow-variable.service';
import { fetchWorkflowDataByRegistrationId, fetchWorkflowDataForMultipleRegistrations } from 'src/common/utils/workflow-data.util'; 
import { AppLoggerService } from 'src/common/services/logger.service';

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

  async getWorkflowDataForRegistration(registrationId: number, workflowId?: number): Promise<Record<string, unknown>> {
    if (!workflowId) {
      throw new InifniBadRequestException(
        ERROR_CODES.WORKFLOW_ID_MISSING_IN_PROGRAM,
        null,
        null,
        'workflowId is required for getting workflow data'
      );
    }

    const workflow = await this.workflowConfigurationService.findById(workflowId);
    
    if (!workflow) {
      throw new InifniBadRequestException(
        ERROR_CODES.WORKFLOW_CONFIGURATION_NOTFOUND_BY_ID,
        null,
        null,
        `Workflow configuration not found for workflowId: ${workflowId}`
      );
    }

    // Use catalog-based query system
    const query = await this.workflowVariableService.buildWorkflowQuery(workflowId);
    this.logger.log('Using catalog-based workflow query', { workflowId });
    return await fetchWorkflowDataByRegistrationId(this.dataSource, query, registrationId);
  }

  /**
   * Batch fetch workflow data for multiple registrations with the same workflow.
   * Optimizes N+1 query problem by fetching all registrations in a single query.
   * 
   * @param registrationIds Array of registration IDs
   * @param workflowId Workflow configuration ID
   * @returns Map of registrationId -> workflow data
   */
  async getWorkflowDataForMultipleRegistrations(
    registrationIds: number[],
    workflowId: number,
  ): Promise<Map<number, Record<string, unknown>>> {
    if (registrationIds.length === 0) {
      return new Map();
    }

    if (!workflowId) {
      throw new InifniBadRequestException(
        ERROR_CODES.WORKFLOW_ID_MISSING_IN_PROGRAM,
        null,
        null,
        'workflowId is required for getting workflow data'
      );
    }

    const workflow = await this.workflowConfigurationService.findById(workflowId);
    
    if (!workflow) {
      throw new InifniBadRequestException(
        ERROR_CODES.WORKFLOW_CONFIGURATION_NOTFOUND_BY_ID,
        null,
        null,
        `Workflow configuration not found for workflowId: ${workflowId}`
      );
    }

    // Use catalog-based query system
    const query = await this.workflowVariableService.buildWorkflowQuery(workflowId);
    this.logger.log('Using catalog-based batch workflow query', { workflowId, registrationCount: registrationIds.length });
    
    return await fetchWorkflowDataForMultipleRegistrations(this.dataSource, query, registrationIds);
  }

  async validateStageTransition(
    registrationId: number,
    workflowId: number,
    targetStage: WorkflowStage,
    completedStages: string[],
  ): Promise<Record<string, unknown>> {
    const workflowData = await this.getWorkflowDataForRegistration(registrationId, workflowId);
    this.validateStageCompletion(targetStage, completedStages, workflowData);
    return workflowData;
  }

  validateStageCompletion(
    targetStage: WorkflowStage,
    completedStages: string[],
    workflowData: Record<string, unknown>,
  ): void {
    const prerequisites = (targetStage.prerequisiteRulesConfig ?? {}) as {
      requiredStages?: string[];
      conditions?: Array<{ condition?: Record<string, unknown> }>;
    };

    const requiredStages = prerequisites.requiredStages ?? [];
    const hasMissingStage = requiredStages.some(stageKey => !completedStages.includes(stageKey));
    if (hasMissingStage) {
      throw new InifniBadRequestException(ERROR_CODES.WORKFLOW_REQUIRED_STAGE_MISSING, null, null, targetStage.stageKey);
    }

    const conditions = prerequisites.conditions ?? [];
    for (const conditionBlock of conditions) {
      if (!conditionBlock.condition) {
        continue;
      }
      const isValid = this.evaluateNestedCondition(
        conditionBlock.condition as Record<string, unknown>,
        workflowData,
      );
      if (!isValid) {
        throw new InifniBadRequestException(ERROR_CODES.WORKFLOW_STAGE_CONDITION_NOT_MET, null, null, targetStage.stageKey);
      }
    }
  }

  /**
   * Evaluate a single field condition with simple operators
   */
  private evaluateCondition(fieldValue: unknown, operator: string, value: unknown): boolean {
    switch (operator) {
      case 'EQUALS':
        return Array.isArray(value) ? value.includes(fieldValue as never) : fieldValue === value;
      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;
      default:
        this.logger.warn('Unknown operator in evaluateCondition', { operator, fieldValue, value });
        return false; // Deny access for unknown operators instead of allowing
    }
  }

  /**
   * Evaluate nested conditions (supports OR, AND operators and simple field conditions)
   */
  private evaluateNestedCondition(
    condition: Record<string, unknown>,
    workflowData: Record<string, unknown>,
  ): boolean {
    const operator = condition.operator as string;

    // Handle logical operators (OR, AND)
    if (operator === 'OR' || operator === 'AND') {
      const conditions = condition.conditions as Record<string, unknown>[];
      if (!conditions || !Array.isArray(conditions)) {
        this.logger.warn('OR/AND operator requires conditions array', { condition });
        return false;
      }

      if (operator === 'OR') {
        // At least one condition must be true
        return conditions.some(cond => this.evaluateNestedCondition(cond, workflowData));
      } else {
        // All conditions must be true
        return conditions.every(cond => this.evaluateNestedCondition(cond, workflowData));
      }
    }

    // Handle simple field conditions
    const field = condition.field as string;
    const value = condition.value;

    if (!field) {
      this.logger.warn('Field condition requires field property', { condition });
      return false;
    }

    const fieldValue = workflowData[field];
    return this.evaluateCondition(fieldValue, operator, value);
  }

  /**
   * Validate stage prerequisites (requiresAuth, requiredStages, conditions with roles)
   * This is called after getting the stage key but before validating sections
   */
  async validateStagePrerequisites(
    stage: WorkflowStage,
    userRoles: string[],
    registrationId?: number,
    workflowId?: number,
  ): Promise<void> {
    const prerequisites = (stage.prerequisiteRulesConfig ?? {}) as {
      conditions?: Array<{
        roles?: string[];
        condition?: Record<string, unknown>;
        message?: string;
      }>;
    };

    // Validate conditions (roles and field conditions)
    const conditions = prerequisites.conditions ?? [];
    
    // If no conditions defined, allow access (no condition restrictions)
    if (conditions.length === 0) {
      return;
    }


    let matched = false;
    let lastError: InifniBadRequestException | null = null;
    for (const conditionBlock of conditions) {
      // Check role restrictions
      let hasRequiredRole = true;
      if (conditionBlock.roles && conditionBlock.roles.length > 0) {
        hasRequiredRole = conditionBlock.roles.some(role => userRoles.includes(role));
      }

      // Check field conditions if present and registrationId is provided
      let fieldConditionValid = true;
      if (conditionBlock.condition && registrationId && workflowId) {
        const workflowData = await this.getWorkflowDataForRegistration(registrationId, workflowId);
        fieldConditionValid = this.evaluateNestedCondition(
          conditionBlock.condition as Record<string, unknown>,
          workflowData,
        );
      }

      if (hasRequiredRole && fieldConditionValid) {
        matched = true;
        break;
      } else {
        // Prepare the most relevant error for later if needed
        if (!hasRequiredRole) {
          const message = conditionBlock.message || `Stage ${stage.stageKey} requires one of these roles: ${conditionBlock.roles?.join(', ')}`;
          lastError = new InifniBadRequestException(
            ERROR_CODES.WORKFLOW_ROLE_REQUIRED,
            null,
            null,
            message
          );
        } else if (!fieldConditionValid) {
          const { field } = conditionBlock.condition as any;
          const message = conditionBlock.message || `Stage ${stage.stageKey} condition not met for field: ${field}`;
          lastError = new InifniBadRequestException(
            ERROR_CODES.WORKFLOW_STAGE_CONDITION_NOT_MET,
            null,
            null,
            message
          );
        }
      }
    }
    if (!matched) {
      // If no block matched, throw the last relevant error
      throw lastError || new InifniBadRequestException(
        ERROR_CODES.WORKFLOW_ROLE_REQUIRED,
        null,
        null,
        `No valid workflow prerequisite block matched for stage ${stage.stageKey}`
      );
    }
  }
}