import { Injectable, BadRequestException, NotFoundException, Inject } from '@nestjs/common';
import { WorkflowState as WorkflowStateModel } from '../../../database/models/workflow-state.model';
import { Workflow } from '../../../database/models/workflow.model';
import {
  WorkflowState,
  PendingApproval,
  CompletedStage,
} from '../interfaces/workflow-state.interface';
import {
  WorkflowProcessResult,
  DocumentUpdateResult,
  StageCompletionCheck,
} from '../interfaces/workflow-process-result.interface';
import { WorkflowDefinition, WorkflowStage } from '../interfaces/workflow-definition.interface';
import { WorkflowAction } from '../enums/workflow-action.enum';
import { CompletionCriteria } from '../enums/completion-criteria.enum';
import { RejectionHandling } from '../enums/rejection-handling.enum';
import { ApprovalStatus } from '../../../common/enums';
import { ApprovalsService } from '../../approvals/approvals.service';
import { ApprovalResponseDto } from '../../approvals/dto/approval-response.dto';

@Injectable()
export class WorkflowExecutorService {
  constructor(
    @Inject(WorkflowStateModel)
    private workflowStateRepository: WorkflowStateModel,
    @Inject(Workflow)
    private workflowRepository: typeof Workflow,
    private approvalsService: ApprovalsService,
  ) {}

  /**
   * Initialize workflow state for a new request
   */
  async initializeWorkflow(requestId: string, workflowId: string): Promise<WorkflowState> {
    const workflow = await this.workflowRepository.query().findById(workflowId);

    if (!workflow || !workflow.isActive) {
      throw new NotFoundException(`Active workflow ${workflowId} not found`);
    }

    const definition = workflow.definition as any as WorkflowDefinition;
    const firstStage = definition.stages[0];

    if (!firstStage) {
      throw new BadRequestException('Workflow has no stages defined');
    }

    const pendingApprovals: PendingApproval[] = (firstStage.requiredApprovals || []).map(
      approval => ({
        departmentCode: approval.departmentCode,
        departmentName: approval.departmentName,
        approvalId: '', // Will be populated when approval records are created
        status: ApprovalStatus.PENDING,
      }),
    );

    const state: WorkflowState = {
      requestId,
      workflowId,
      currentStageId: firstStage.stageId,
      currentStageOrder: firstStage.stageOrder,
      completedStages: [],
      pendingApprovals,
      isWorkflowComplete: false,
      startedAt: new Date(),
      metadata: {
        initiatedAt: new Date().toISOString(),
      },
    };

    await (this.workflowStateRepository.constructor as typeof WorkflowStateModel).query().insert({
      requestId,
      workflowId,
      state,
    });

    return state;
  }

  /**
   * Get workflow state
   */
  async getWorkflowState(requestId: string): Promise<WorkflowState | null> {
    const stateEntity = await (
      this.workflowStateRepository.constructor as typeof WorkflowStateModel
    )
      .query()
      .findOne({
        requestId,
      });

    return stateEntity ? stateEntity.state : null;
  }

  /**
   * Save workflow state
   */
  async saveWorkflowState(state: WorkflowState): Promise<void> {
    const stateEntity = await (
      this.workflowStateRepository.constructor as typeof WorkflowStateModel
    )
      .query()
      .findOne({
        requestId: state.requestId,
      });

    if (stateEntity) {
      await stateEntity.$query().patch({ state });
    } else {
      await (this.workflowStateRepository.constructor as typeof WorkflowStateModel).query().insert({
        requestId: state.requestId,
        workflowId: state.workflowId,
        state,
      });
    }
  }

  /**
   * Process an approval and advance workflow
   */
  async processApproval(
    requestId: string,
    departmentCode: string,
    approvalStatus: ApprovalStatus,
  ): Promise<WorkflowProcessResult> {
    let state = await this.getWorkflowState(requestId);

    if (!state) {
      throw new NotFoundException(`No workflow state found for request ${requestId}`);
    }

    if (state.isWorkflowComplete) {
      throw new BadRequestException('Workflow is already complete');
    }

    // Update pending approval status
    const pendingApproval = state.pendingApprovals.find(pa => pa.departmentCode === departmentCode);

    if (!pendingApproval) {
      throw new BadRequestException(`No pending approval found for department ${departmentCode}`);
    }

    pendingApproval.status = approvalStatus;

    const actions: WorkflowAction[] = [];
    let stageAdvanced = false;
    let workflowCompleted = false;
    let failureReason: string | undefined;

    // Check if stage is complete
    const stageComplete = await this.isStageComplete(state);

    if (stageComplete.isComplete) {
      const workflow = await this.workflowRepository.query().findById(state.workflowId);
      const definition = workflow.definition as any as WorkflowDefinition;
      const currentStage = definition.stages.find(s => s.stageId === state.currentStageId);

      if (stageComplete.rejectionCount > 0) {
        // Handle rejection based on stage configuration
        const rejectionAction = await this.handleRejection(state, currentStage, stageComplete);
        actions.push(...rejectionAction.actions);
        failureReason = rejectionAction.failureReason;

        if (rejectionAction.failureReason) {
          state.isWorkflowComplete = true;
          state.workflowFailureReason = rejectionAction.failureReason;
        } else {
          stageAdvanced = true;
          state = rejectionAction.updatedState;
        }
      } else {
        // All approved, advance to next stage or complete
        const nextStage = this.getNextStage(state, definition);

        if (nextStage) {
          state = await this.advanceToNextStage(state, nextStage);
          stageAdvanced = true;
          actions.push(WorkflowAction.ADVANCE_STAGE);
          actions.push(WorkflowAction.NOTIFY_NEXT_DEPT);
        } else {
          state.isWorkflowComplete = true;
          state.completedAt = new Date();
          stageAdvanced = false;
          workflowCompleted = true;
          actions.push(WorkflowAction.COMPLETE_WORKFLOW);
          actions.push(WorkflowAction.NOTIFY_APPLICANT);
          actions.push(WorkflowAction.MINT_NFT);
          actions.push(WorkflowAction.ISSUE_CERTIFICATE);
        }
      }
    }

    await this.saveWorkflowState(state);

    return {
      success: !failureReason,
      state,
      actions,
      actionsMetadata: {
        currentStageId: state.currentStageId,
        nextDepartments: stageAdvanced ? state.pendingApprovals.map(pa => pa.departmentCode) : [],
      },
      stageAdvanced,
      workflowCompleted,
      failureReason,
      nextDepartments: state.pendingApprovals.map(pa => pa.departmentCode),
      message: `Approval processed for department ${departmentCode}`,
    };
  }

  /**
   * Check if current stage is complete
   */
  async isStageComplete(state: WorkflowState): Promise<StageCompletionCheck> {
    const workflow = await this.workflowRepository.query().findById(state.workflowId);
    const definition = workflow.definition as any as WorkflowDefinition;
    const currentStage = definition.stages.find(s => s.stageId === state.currentStageId);

    const pendingApprovals = state.pendingApprovals;

    const approvedCount = pendingApprovals.filter(
      pa => pa.status === ApprovalStatus.APPROVED,
    ).length;

    const rejectionCount = pendingApprovals.filter(
      pa => pa.status === ApprovalStatus.REJECTED,
    ).length;

    const pendingCount = pendingApprovals.filter(pa => pa.status === ApprovalStatus.PENDING).length;

    const totalRequired = pendingApprovals.length;

    let isComplete = false;

    switch (currentStage.completionCriteria) {
      case CompletionCriteria.ALL:
        // All must approve (even one rejection fails)
        isComplete = rejectionCount === 0 && pendingCount === 0;
        break;

      case CompletionCriteria.ANY:
        // At least one must approve
        isComplete = approvedCount > 0 || pendingCount === 0;
        break;

      case CompletionCriteria.THRESHOLD:
        // Minimum threshold met
        const threshold = currentStage.threshold || 1;
        isComplete = approvedCount >= threshold || approvedCount + pendingCount < threshold;
        break;
    }

    return {
      isComplete,
      approvedCount,
      totalRequired,
      rejectionCount,
      pendingCount,
    };
  }

  /**
   * Move to next stage
   */
  private async advanceToNextStage(
    state: WorkflowState,
    nextStage: WorkflowStage,
  ): Promise<WorkflowState> {
    const workflow = await this.workflowRepository.query().findById(state.workflowId);
    const definition = workflow.definition as any as WorkflowDefinition;
    const currentStage = definition.stages.find(s => s.stageId === state.currentStageId);

    // Mark current stage as complete
    const completedStage: CompletedStage = {
      stageId: state.currentStageId,
      stageName: currentStage.stageName,
      stageOrder: state.currentStageOrder,
      completedAt: new Date(),
      approvals: [...state.pendingApprovals],
    };

    state.completedStages.push(completedStage);

    // Update to next stage
    state.currentStageId = nextStage.stageId;
    state.currentStageOrder = nextStage.stageOrder;

    // Initialize pending approvals for new stage
    state.pendingApprovals = nextStage.requiredApprovals.map(approval => ({
      departmentCode: approval.departmentCode,
      departmentName: approval.departmentName,
      approvalId: '',
      status: ApprovalStatus.PENDING,
    }));

    return state;
  }

  /**
   * Handle document update - invalidate approvals
   */
  async handleDocumentUpdate(requestId: string, documentId: string): Promise<DocumentUpdateResult> {
    const state = await this.getWorkflowState(requestId);

    if (!state || state.isWorkflowComplete) {
      return {
        affectedDepartments: [],
        invalidatedApprovals: [],
        requiresResubmission: false,
        message: 'Workflow is not in progress',
      };
    }

    const affectedDepartments = await this.approvalsService.invalidateApprovalsByDocument(
      requestId,
      documentId,
      'Document was updated',
    );

    // Reset pending approvals for affected departments
    for (const deptCode of affectedDepartments) {
      const pendingApproval = state.pendingApprovals.find(pa => pa.departmentCode === deptCode);

      if (pendingApproval) {
        pendingApproval.status = ApprovalStatus.PENDING;
        pendingApproval.invalidatedAt = new Date();
      }
    }

    await this.saveWorkflowState(state);

    return {
      affectedDepartments,
      invalidatedApprovals: affectedDepartments,
      requiresResubmission: true,
      message: `Approvals invalidated for departments: ${affectedDepartments.join(', ')}`,
    };
  }

  /**
   * Check if department can approve at current stage
   */
  async canDepartmentApprove(requestId: string, departmentCode: string): Promise<boolean> {
    const state = await this.getWorkflowState(requestId);

    if (!state || state.isWorkflowComplete) {
      return false;
    }

    const workflow = await this.workflowRepository.query().findById(state.workflowId);
    const definition = workflow.definition as any as WorkflowDefinition;
    const currentStage = definition.stages.find(s => s.stageId === state.currentStageId);

    const isInCurrentStage = currentStage.requiredApprovals.some(
      ra => ra.departmentCode === departmentCode,
    );

    if (!isInCurrentStage) {
      return false;
    }

    const pendingApproval = state.pendingApprovals.find(pa => pa.departmentCode === departmentCode);

    return pendingApproval?.status === ApprovalStatus.PENDING;
  }

  /**
   * Get next pending departments
   */
  async getNextPendingDepartments(requestId: string): Promise<string[]> {
    const state = await this.getWorkflowState(requestId);

    if (!state) {
      return [];
    }

    return state.pendingApprovals
      .filter(pa => pa.status === ApprovalStatus.PENDING)
      .map(pa => pa.departmentCode);
  }

  /**
   * Check if workflow is complete
   */
  async isWorkflowComplete(requestId: string): Promise<boolean> {
    const state = await this.getWorkflowState(requestId);
    return state?.isWorkflowComplete ?? false;
  }

  /**
   * Get workflow progress
   */
  async getWorkflowProgress(requestId: string) {
    const state = await this.getWorkflowState(requestId);

    if (!state) {
      return null;
    }

    const workflow = await this.workflowRepository.query().findById(state.workflowId);

    const totalStages = (workflow?.definition as WorkflowDefinition).stages.length;
    const completedStagesCount = state.completedStages.length;

    return {
      requestId,
      currentStage: state.currentStageOrder,
      totalStages,
      completedStages: completedStagesCount,
      progress: ((completedStagesCount + 1) / totalStages) * 100,
      isComplete: state.isWorkflowComplete,
      failureReason: state.workflowFailureReason,
    };
  }

  /**
   * Helper: Get next stage
   */
  private getNextStage(state: WorkflowState, definition: WorkflowDefinition): WorkflowStage | null {
    const currentStageIndex = definition.stages.findIndex(s => s.stageId === state.currentStageId);
    if (currentStageIndex < definition.stages.length - 1) {
      return definition.stages[currentStageIndex + 1];
    }
    return null;
  }

  /**
   * Helper: Handle rejection
   */
  private async handleRejection(
    state: WorkflowState,
    stage: WorkflowStage,
    check: StageCompletionCheck,
  ): Promise<{
    actions: WorkflowAction[];
    failureReason?: string;
    updatedState: WorkflowState;
  }> {
    const actions: WorkflowAction[] = [];
    let failureReason: string | undefined;

    switch (stage.rejectionHandling) {
      case RejectionHandling.FAIL_REQUEST:
        actions.push(WorkflowAction.FAIL_REQUEST);
        failureReason = `Request rejected at stage ${stage.stageName}`;
        break;

      case RejectionHandling.RETRY_STAGE:
        // Reset all approvals in current stage to PENDING
        state.pendingApprovals = state.pendingApprovals.map(pa => ({
          ...pa,
          status: ApprovalStatus.PENDING,
        }));
        actions.push(WorkflowAction.RETRY_STAGE);
        actions.push(WorkflowAction.NOTIFY_NEXT_DEPT);
        break;

      case RejectionHandling.ESCALATE:
        if (stage.escalationDepartment) {
          // Add escalation department to pending approvals
          state.pendingApprovals.push({
            departmentCode: stage.escalationDepartment,
            departmentName: stage.escalationDepartment,
            approvalId: '',
            status: ApprovalStatus.PENDING,
          });
          actions.push(WorkflowAction.ESCALATE);
          actions.push(WorkflowAction.NOTIFY_NEXT_DEPT);
        } else {
          failureReason = `Escalation requested but no escalation department configured`;
        }
        break;
    }

    return {
      actions,
      failureReason,
      updatedState: state,
    };
  }

  /**
   * Evaluate stage completion criteria
   */
  private evaluateCompletionCriteria(
    stage: WorkflowStage,
    approvals: ApprovalResponseDto[],
  ): boolean {
    const approvedCount = approvals.filter(a => a.status === ApprovalStatus.APPROVED).length;

    switch (stage.completionCriteria) {
      case CompletionCriteria.ALL:
        return approvals.every(a => a.status === ApprovalStatus.APPROVED);

      case CompletionCriteria.ANY:
        return approvals.some(a => a.status === ApprovalStatus.APPROVED);

      case CompletionCriteria.THRESHOLD:
        return approvedCount >= (stage.threshold || 1);

      default:
        return false;
    }
  }
}
