import { Injectable, NotFoundException, BadRequestException, Inject } from '@nestjs/common';

import { Workflow } from '../../../database/models/workflow.model';
import { CreateWorkflowDto } from '../dto/create-workflow.dto';
import { UpdateWorkflowDto } from '../dto/update-workflow.dto';
import {
  WorkflowDefinition,
  ValidationResult,
  WorkflowStage,
} from '../interfaces/workflow-definition.interface';
import { WorkflowPreview } from '../interfaces/workflow-state.interface';
import { CompletionCriteria } from '../enums/completion-criteria.enum';

@Injectable()
export class WorkflowsService {
  constructor(
    @Inject(Workflow)
    private workflowRepository: typeof Workflow,
  ) {}

  /**
   * Create a new workflow
   */
  async create(dto: CreateWorkflowDto): Promise<Workflow> {
    // Validate workflow definition
    const validation = this.validateDefinition({
      workflowType: dto.workflowType,
      name: dto.name,
      description: dto.description,
      version: 1,
      stages: dto.stages as unknown as WorkflowStage[],
      onSuccessActions: dto.onSuccessActions,
      onFailureActions: dto.onFailureActions,
      metadata: dto.metadata,
    });

    if (!validation.isValid) {
      throw new BadRequestException(`Workflow validation failed: ${validation.errors.join(', ')}`);
    }

    const definition: WorkflowDefinition = {
      workflowType: dto.workflowType,
      name: dto.name,
      description: dto.description,
      version: 1,
      stages: dto.stages as unknown as WorkflowStage[],
      onSuccessActions: dto.onSuccessActions,
      onFailureActions: dto.onFailureActions,
      metadata: dto.metadata,
    };

    return this.workflowRepository.query().insertAndFetch({
      workflowType: dto.workflowType,
      name: dto.name,
      description: dto.description,
      definition: definition as any,
      version: 1,
      isActive: true,
      createdBy: dto.createdBy,
    });
  }

  /**
   * Get all workflows
   */
  async findAll(isActive?: boolean): Promise<Workflow[]> {
    const query = this.workflowRepository.query();

    if (isActive !== undefined) {
      query.where({ is_active: isActive });
    }

    return query.orderBy('created_at', 'DESC');
  }

  /**
   * Get workflow by ID
   */
  async findById(id: string): Promise<Workflow> {
    const workflow = await this.workflowRepository.query().findById(id);

    if (!workflow) {
      throw new NotFoundException(`Workflow ${id} not found`);
    }

    return workflow;
  }

  /**
   * Get workflow by type
   */
  async findByType(workflowType: string): Promise<Workflow> {
    const workflow = await this.workflowRepository
      .query()
      .findOne({ workflowType, isActive: true })
      .orderBy('version', 'DESC');

    if (!workflow) {
      throw new NotFoundException(`No active workflow found for type ${workflowType}`);
    }

    return workflow;
  }

  /**
   * Update workflow
   */
  async update(id: string, dto: UpdateWorkflowDto): Promise<Workflow> {
    const workflow = await this.findById(id);

    if (!workflow.isActive) {
      throw new BadRequestException('Cannot update an inactive workflow');
    }

    const patch: Partial<Workflow> = {};
    let definition = workflow.definition as any as WorkflowDefinition;

    // If stages are being updated, validate the new definition
    if (dto.stages) {
      const newDefinition = {
        ...definition,
        ...dto,
        stages: dto.stages as unknown as WorkflowStage[],
      };
      const validation = this.validateDefinition(newDefinition);

      if (!validation.isValid) {
        throw new BadRequestException(
          `Workflow validation failed: ${validation.errors.join(', ')}`,
        );
      }

      patch.version = workflow.version + 1;
      definition = newDefinition;
    }

    // Update fields
    if (dto.name) patch.name = dto.name;
    if (dto.description) patch.description = dto.description;
    if (dto.metadata) (patch as any).metadata = dto.metadata;
    if (dto.onSuccessActions) definition.onSuccessActions = dto.onSuccessActions;
    if (dto.onFailureActions) definition.onFailureActions = dto.onFailureActions;
    patch.definition = definition;
    if (dto.updatedBy) (patch as any).updatedBy = dto.updatedBy;

    return workflow.$query().patchAndFetch(patch);
  }

  /**
   * Deactivate workflow
   */
  async deactivate(id: string): Promise<void> {
    const workflow = await this.findById(id);
    await workflow.$query().patch({ isActive: false, deactivatedAt: new Date() });
  }

  /**
   * Activate workflow
   */
  async activate(id: string): Promise<void> {
    const workflow = await this.findById(id);
    await workflow.$query().patch({ isActive: true, deactivatedAt: null });
  }

  /**
   * Validate workflow definition
   */
  validateDefinition(definition: WorkflowDefinition): ValidationResult {
    const errors: string[] = [];
    const warnings: string[] = [];

    // Check basic fields
    if (!definition.workflowType) {
      errors.push('workflowType is required');
    }

    if (!definition.name) {
      errors.push('name is required');
    }

    if (!definition.stages || definition.stages.length === 0) {
      errors.push('At least one stage is required');
    } else {
      // Validate stages
      const stageIds = new Set<string>();

      definition.stages.forEach((stage, index) => {
        // Check stage ID uniqueness
        if (stageIds.has(stage.stageId)) {
          errors.push(`Duplicate stageId: ${stage.stageId}`);
        }
        stageIds.add(stage.stageId);

        // Check required fields
        if (!stage.stageName) {
          errors.push(`Stage ${index} is missing stageName`);
        }

        if (stage.stageOrder !== index) {
          warnings.push(`Stage ${index} has stageOrder ${stage.stageOrder}, expected ${index}`);
        }

        if (!stage.requiredApprovals || stage.requiredApprovals.length === 0) {
          errors.push(`Stage ${stage.stageId} has no required approvals`);
        }

        // Validate completion criteria
        if (stage.completionCriteria === CompletionCriteria.THRESHOLD) {
          if (!stage.threshold || stage.threshold < 1) {
            errors.push(`Stage ${stage.stageId} uses THRESHOLD but no valid threshold is set`);
          }

          if (stage.threshold && stage.threshold > stage.requiredApprovals.length) {
            errors.push(`Stage ${stage.stageId} threshold exceeds number of required approvals`);
          }
        }

        // Validate rejection handling
        if (stage.rejectionHandling === 'ESCALATE' && !stage.escalationDepartment) {
          warnings.push(
            `Stage ${stage.stageId} uses ESCALATE but no escalationDepartment is configured`,
          );
        }
      });
    }

    return {
      isValid: errors.length === 0,
      errors,
      warnings,
    };
  }

  /**
   * Preview workflow structure
   */
  async preview(id: string): Promise<WorkflowPreview> {
    const workflow = await this.findById(id);
    const definition = workflow.definition as any as WorkflowDefinition;

    return {
      workflowId: workflow.id,
      workflowType: workflow.workflowType,
      name: workflow.name,
      stages: definition.stages.map(stage => ({
        stageId: stage.stageId,
        stageName: stage.stageName,
        stageOrder: stage.stageOrder,
        departments: stage.requiredApprovals.map(ra => ra.departmentCode),
        executionType: stage.executionType,
        completionCriteria: stage.completionCriteria,
      })),
    };
  }
}
