import {
  Injectable,
  NotFoundException,
  BadRequestException,
  ConflictException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import {
  CreateWorkflowRoleDto,
  UpdateWorkflowRoleDto,
  WorkflowRoleQueryDto,
  ResolveWorkflowRoleDto,
  ResolveResult,
  ResolvedUser,
  WorkflowRuleType,
} from './dto/workflow-role.dto';
import { 
  IamWorkflowRoleResolveEmptyException,
  IamUserNotInDepartmentException,
} from '../exceptions';

@Injectable()
export class WorkflowRolesService {
  private readonly logger = new Logger(WorkflowRolesService.name);

  constructor(private prisma: PrismaService) {}

  /**
   * Get all workflow roles
   */
  async findAll(query?: WorkflowRoleQueryDto) {
    const where = query?.keyword
      ? {
          OR: [
            { name: { contains: query.keyword, mode: 'insensitive' as const } },
            { code: { contains: query.keyword, mode: 'insensitive' as const } },
            { description: { contains: query.keyword, mode: 'insensitive' as const } },
          ],
        }
      : {};

    const workflowRoles = await this.prisma.workflowRole.findMany({
      where,
      include: {
        _count: {
          select: {
            userAssignments: true,
          },
        },
      },
      orderBy: { createdAt: 'desc' },
    });

    return workflowRoles.map((role) => ({
      ...role,
      userCount: role._count.userAssignments,
    }));
  }

  /**
   * Get workflow role by ID
   */
  async findOne(id: string) {
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { id },
      include: {
        userAssignments: {
          include: {
            user: {
              select: {
                id: true,
                username: true,
                displayName: true,
                email: true,
                avatar: true,
                departmentMemberships: {
                  where: { isPrimary: true },
                  include: {
                    department: {
                      select: { id: true, name: true },
                    },
                  },
                },
              },
            },
          },
        },
      },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with ID ${id} not found`);
    }

    return {
      ...workflowRole,
      users: (workflowRole as any).userAssignments?.map((wa: any) => wa.user) || [],
    };
  }

  /**
   * Create workflow role
   */
  async create(createDto: CreateWorkflowRoleDto) {
    // Check if code or name already exists
    const existingRole = await this.prisma.workflowRole.findFirst({
      where: {
        OR: [{ code: createDto.code }, { name: createDto.name }],
      },
    });

    if (existingRole) {
      throw new ConflictException(
        existingRole.code === createDto.code
          ? `Workflow role code '${createDto.code}' already exists`
          : `Workflow role name '${createDto.name}' already exists`,
      );
    }

    // Validate rule config
    this.validateRuleConfig(createDto.ruleType, createDto.ruleConfig);

    return this.prisma.workflowRole.create({
      data: {
        name: createDto.name,
        code: createDto.code,
        description: createDto.description,
        ruleType: createDto.ruleType,
        ruleConfig: createDto.ruleConfig || {},
      },
    });
  }

  /**
   * Update workflow role
   */
  async update(id: string, updateDto: UpdateWorkflowRoleDto) {
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { id },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with ID ${id} not found`);
    }

    // Check for name conflicts
    if (updateDto.name) {
      const existingRole = await this.prisma.workflowRole.findFirst({
        where: {
          name: updateDto.name,
          id: { not: id },
        },
      });

      if (existingRole) {
        throw new ConflictException(`Workflow role name '${updateDto.name}' already exists`);
      }
    }

    // Validate rule config
    if (updateDto.ruleType || updateDto.ruleConfig) {
      this.validateRuleConfig(
        updateDto.ruleType || workflowRole.ruleType,
        updateDto.ruleConfig || (workflowRole.ruleConfig as any),
      );
    }

    return this.prisma.workflowRole.update({
      where: { id },
      data: updateDto,
    });
  }

  /**
   * Delete workflow role
   */
  async remove(id: string) {
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { id },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with ID ${id} not found`);
    }

    // TODO: Check if any approval workflows are using this role

    await this.prisma.workflowRole.delete({ where: { id } });

    return { message: 'Workflow role deleted successfully' };
  }

  /**
   * Get workflow role users
   */
  async getUsers(id: string) {
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { id },
      include: {
        userAssignments: {
          include: {
            user: {
              select: {
                id: true,
                username: true,
                displayName: true,
                email: true,
                avatar: true,
                status: true,
                departmentMemberships: {
                  where: { isPrimary: true },
                  include: {
                    department: {
                      select: { id: true, name: true },
                    },
                    position: {
                      select: { id: true, name: true },
                    },
                  },
                },
              },
            },
          },
        },
      },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with ID ${id} not found`);
    }

    return (workflowRole as any).userAssignments?.map((wa: any) => wa.user) || [];
  }

  /**
   * Assign users to workflow role
   */
  async assignUsers(id: string, userIds: string[]) {
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { id },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with ID ${id} not found`);
    }

    // Only FIXED_USERS type supports user assignment
    if (workflowRole.ruleType !== 'FIXED_USERS') {
      throw new BadRequestException(
        `Cannot assign users to workflow role with type '${workflowRole.ruleType}'. Only 'FIXED_USERS' type supports user assignment.`,
      );
    }

    // Validate user IDs
    const users = await this.prisma.user.findMany({
      where: { id: { in: userIds }, deletedAt: null },
    });

    if (users.length !== userIds.length) {
      throw new NotFoundException('Some user IDs are invalid');
    }

    // Get existing assignments
    const existing = await this.prisma.workflowRoleUser.findMany({
      where: {
        workflowRoleId: id,
        userId: { in: userIds },
      },
    });

    const existingUserIds = existing.map((wru) => wru.userId);
    const newUserIds = userIds.filter((userId) => !existingUserIds.includes(userId));

    // Create only non-existing associations
    if (newUserIds.length > 0) {
      await this.prisma.workflowRoleUser.createMany({
        data: newUserIds.map((userId) => ({
          workflowRoleId: id,
          userId,
        })),
      });
    }

    return this.getUsers(id);
  }

  /**
   * Remove user from workflow role
   */
  async removeUser(id: string, userId: string) {
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { id },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with ID ${id} not found`);
    }

    const assignment = await this.prisma.workflowRoleUser.findFirst({
      where: { workflowRoleId: id, userId },
    });

    if (!assignment) {
      throw new NotFoundException(`User is not assigned to this workflow role`);
    }

    await this.prisma.workflowRoleUser.delete({ where: { id: assignment.id } });

    return { message: 'User removed from workflow role successfully' };
  }

  /**
   * Resolve workflow role to actual users
   * This is called by the approval engine to get the actual approvers
   */
  async resolve(dto: ResolveWorkflowRoleDto): Promise<ResolveResult> {
    const { workflowRoleCode, context } = dto;

    // Find the workflow role
    const workflowRole = await this.prisma.workflowRole.findUnique({
      where: { code: workflowRoleCode },
      include: {
        userAssignments: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                status: true,
              },
            },
          },
        },
      },
    });

    if (!workflowRole) {
      throw new NotFoundException(`Workflow role with code '${workflowRoleCode}' not found`);
    }

    const ruleConfig = workflowRole.ruleConfig as Record<string, any>;
    let users: ResolvedUser[] = [];
    let fallbackUsed = false;

    switch (workflowRole.ruleType) {
      case 'FIXED_USERS':
        users = workflowRole.userAssignments
          .filter((wa) => wa.user.status === 'ACTIVE')
          .map((wa) => ({
            userId: wa.user.id,
            displayName: wa.user.displayName,
            email: wa.user.email,
          }));
        break;

      case 'ORGANIZATION_RELATION':
        users = await this.resolveOrganizationRelation(
          context.initiatorUserId, 
          ruleConfig,
          context.formData,
        );
        break;

      case 'SYSTEM_ROLE_MAPPING':
        users = await this.resolveSystemRoleMapping(ruleConfig);
        break;

      case 'DYNAMIC_SCRIPT':
        // Dynamic script resolution is not implemented yet
        this.logger.warn('DYNAMIC_SCRIPT resolution is not implemented');
        break;
    }

    // Apply fallback if no users found
    if (users.length === 0 && ruleConfig.fallbackType) {
      users = await this.applyFallback(context.initiatorUserId, ruleConfig);
      fallbackUsed = users.length > 0;
    }

    // If still no users and no fallback worked, throw error
    if (users.length === 0) {
      throw new IamWorkflowRoleResolveEmptyException(workflowRoleCode);
    }

    // 特殊处理：departmentHeadChain 应该返回 SEQUENTIAL 策略
    let strategy = ruleConfig.strategy || 'ALL';
    if (ruleConfig.relation === 'departmentHeadChain' && users.length > 1) {
      strategy = 'SEQUENTIAL';
    }

    return {
      users,
      strategy,
      resolvedBy: workflowRole.ruleType as WorkflowRuleType,
      fallbackUsed,
    };
  }

  /**
   * Resolve organization relation rule
   * 
   * 解析优先级：
   * 1. 若 context.formData.departmentId 存在：使用该部门的 UserDepartment.managerId
   * 2. 若不存在：使用用户主部门（isPrimary = true）的 managerId
   * 
   * Phase 8: 现在使用 UserDepartment 表管理汇报关系
   */
  private async resolveOrganizationRelation(
    initiatorUserId: string,
    ruleConfig: Record<string, any>,
    formData?: Record<string, any>,
  ): Promise<ResolvedUser[]> {
    const initiator = await this.prisma.user.findUnique({
      where: { id: initiatorUserId, deletedAt: null },
    });

    if (!initiator) {
      return [];
    }

    const relation = ruleConfig.relation;
    const targetDepartmentId = formData?.departmentId;

    switch (relation) {
      case 'manager':
        // 直属上级解析
        let userDepartment;
        
        if (targetDepartmentId) {
          // 若指定了部门，使用该部门的汇报关系
          userDepartment = await this.prisma.userDepartment.findUnique({
            where: {
              userId_departmentId: {
                userId: initiatorUserId,
                departmentId: targetDepartmentId,
              },
            },
            include: {
              manager: {
                select: { id: true, displayName: true, email: true, status: true },
              },
            },
          });
          
          if (!userDepartment) {
            // 用户不属于指定部门，抛出异常
            throw new IamUserNotInDepartmentException(targetDepartmentId);
          }
        } else {
          // 使用主部门的汇报关系
          userDepartment = await this.prisma.userDepartment.findFirst({
            where: {
              userId: initiatorUserId,
              isPrimary: true,
            },
            include: {
              manager: {
                select: { id: true, displayName: true, email: true, status: true },
              },
            },
          });
        }
        
        if (userDepartment?.manager && userDepartment.manager.status === 'ACTIVE') {
          return [{
            userId: userDepartment.manager.id,
            displayName: userDepartment.manager.displayName,
            email: userDepartment.manager.email,
          }];
        }
        break;

      case 'departmentHead':
        // 部门主管解析（支持向上追溯到有主管的父部门）
        let departmentId = targetDepartmentId;
        
        if (!departmentId) {
          // 若未指定部门，使用用户的主部门
          const primaryDept = await this.prisma.userDepartment.findFirst({
            where: {
              userId: initiatorUserId,
              isPrimary: true,
            },
            select: { departmentId: true },
          });
          departmentId = primaryDept?.departmentId;
        }
        
        if (departmentId) {
          // 从当前部门开始向上追溯，直到找到有主管的部门
          let currentDeptId: string | null = departmentId;
          const maxDepth = 10; // 防止无限循环
          let depth = 0;
          
          while (currentDeptId && depth < maxDepth) {
            const department = await this.prisma.department.findUnique({
              where: { id: currentDeptId, deletedAt: null },
              select: { headId: true, parentId: true },
            });
            
            if (!department) {
              break;
            }
            
            // 如果当前部门有主管，返回
            if (department.headId) {
              const deptHead = await this.prisma.user.findUnique({
                where: { id: department.headId, deletedAt: null },
                select: { id: true, displayName: true, email: true, status: true },
              });
              if (deptHead && deptHead.status === 'ACTIVE') {
                return [{
                  userId: deptHead.id,
                  displayName: deptHead.displayName,
                  email: deptHead.email,
                }];
              }
            }
            
            // 向上追溯到父部门
            currentDeptId = department.parentId;
            depth++;
          }
        }
        break;

      case 'departmentHeadChain':
        // 连续部门主管链解析（从用户部门一直到顶级部门或指定级别）
        let startDepartmentId = targetDepartmentId;
        
        if (!startDepartmentId) {
          // 若未指定部门，使用用户的主部门
          const primaryDept = await this.prisma.userDepartment.findFirst({
            where: {
              userId: initiatorUserId,
              isPrimary: true,
            },
            select: { departmentId: true },
          });
          startDepartmentId = primaryDept?.departmentId;
        }
        
        if (startDepartmentId) {
          const headChain: ResolvedUser[] = [];
          let currentDeptId: string | null = startDepartmentId;
          const maxDepth = 20; // 最多20层
          let depth = 0;
          const visitedUsers = new Set<string>(); // 防止重复用户（v2.1.16）
          const visitedDepts = new Set<string>(); // 防止循环引用
          const stopAtLevel = ruleConfig.stopAtLevel; // v2.1.18: 终止级别
          
          while (currentDeptId && depth < maxDepth) {
            // 检测循环引用
            if (visitedDepts.has(currentDeptId)) {
              this.logger.warn(`Circular department reference detected: ${currentDeptId}`);
              break;
            }
            visitedDepts.add(currentDeptId);
            
            const department = await this.prisma.department.findUnique({
              where: { id: currentDeptId, deletedAt: null },
              select: { headId: true, parentId: true, name: true },
            });
            
            if (!department) {
              break;
            }
            
            // 如果当前部门有主管，添加到链中
            if (department.headId) {
              const deptHead = await this.prisma.user.findUnique({
                where: { id: department.headId, deletedAt: null },
                select: { id: true, displayName: true, email: true, status: true },
              });
              
              if (deptHead && deptHead.status === 'ACTIVE') {
                // 避免重复添加同一个主管（v2.1.16: 使用 visitedUsers）
                if (!visitedUsers.has(deptHead.id)) {
                  visitedUsers.add(deptHead.id);
                  headChain.push({
                    userId: deptHead.id,
                    displayName: deptHead.displayName,
                    email: deptHead.email,
                  });
                }
              }
            }
            
            // v2.1.18: 准备追溯到父部门前，检查父部门的level是否低于终止level
            if (stopAtLevel !== undefined && stopAtLevel !== null && department.parentId) {
              // 计算父部门的层级
              const parentLevel = await this.calculateDepartmentLevel(department.parentId);
              // 如果父部门层级低于终止层级（parentLevel < stopAtLevel），不再追溯
              if (parentLevel !== null && parentLevel < stopAtLevel) {
                break;
              }
            }
            
            // 向上追溯到父部门
            currentDeptId = department.parentId;
            depth++;
          }
          
          return headChain;
        }
        break;
    }

    return [];
  }

  /**
   * Resolve system role mapping
   */
  private async resolveSystemRoleMapping(
    ruleConfig: Record<string, any>,
  ): Promise<ResolvedUser[]> {
    const { roleId, roleCode } = ruleConfig;

    const roleWhere = roleId ? { id: roleId } : { code: roleCode };
    
    const role = await this.prisma.role.findFirst({
      where: { ...roleWhere, enabled: true },
      include: {
        users: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                status: true,
                deletedAt: true,
              },
            },
          },
        },
      },
    });

    if (!role) {
      return [];
    }

    return role.users
      .filter((ur) => ur.user.status === 'ACTIVE' && !ur.user.deletedAt)
      .map((ur) => ({
        userId: ur.user.id,
        displayName: ur.user.displayName,
        email: ur.user.email,
      }));
  }

  /**
   * Apply fallback strategy
   */
  private async applyFallback(
    initiatorUserId: string,
    ruleConfig: Record<string, any>,
  ): Promise<ResolvedUser[]> {
    const { fallbackType, fallbackConfig } = ruleConfig;

    switch (fallbackType) {
      case 'UP_CHAIN': {
        // Traverse up the manager chain using UserDepartment
        const maxLevel = fallbackConfig?.maxLevel || 2;
        let currentUserId = initiatorUserId;

        for (let level = 0; level < maxLevel; level++) {
          // 使用主部门的汇报关系
          const userDept = await this.prisma.userDepartment.findFirst({
            where: {
              userId: currentUserId,
              isPrimary: true,
            },
            select: { managerId: true },
          });

          if (!userDept?.managerId) break;

          const manager = await this.prisma.user.findUnique({
            where: { id: userDept.managerId, deletedAt: null },
            select: { id: true, displayName: true, email: true, status: true },
          });

          if (manager && manager.status === 'ACTIVE') {
            return [{
              userId: manager.id,
              displayName: manager.displayName,
              email: manager.email,
            }];
          }

          currentUserId = userDept.managerId;
        }
        break;
      }

      case 'FIXED_USER': {
        const { userId } = fallbackConfig || {};
        if (userId) {
          const user = await this.prisma.user.findUnique({
            where: { id: userId, deletedAt: null },
            select: { id: true, displayName: true, email: true, status: true },
          });
          if (user && user.status === 'ACTIVE') {
            return [{
              userId: user.id,
              displayName: user.displayName,
              email: user.email,
            }];
          }
        }
        break;
      }

      case 'SYSTEM_ROLE': {
        const { roleCode } = fallbackConfig || {};
        if (roleCode) {
          return this.resolveSystemRoleMapping({ roleCode });
        }
        break;
      }
    }

    return [];
  }

  /**
   * Calculate department level (v2.1.18)
   * 
   * 动态计算部门所在层级（从顶级部门开始计数）:
   * - level 0: 顶级部门（parentId = null）
   * - level 1: 一级部门（parent 是顶级部门）
   * - level 2: 二级部门
   * - ...
   * 
   * @param departmentId 部门ID
   * @returns 部门层级，如果部门不存在返回 null
   */
  private async calculateDepartmentLevel(departmentId: string): Promise<number | null> {
    let currentDeptId: string | null = departmentId;
    let level = 0;
    const maxDepth = 20; // 防止无限循环
    const visited = new Set<string>();

    while (currentDeptId && level < maxDepth) {
      // 检测循环引用
      if (visited.has(currentDeptId)) {
        this.logger.warn(`Circular reference detected when calculating level for department ${departmentId}`);
        return null;
      }
      visited.add(currentDeptId);

      const department: { parentId: string | null } | null = await this.prisma.department.findUnique({
        where: { id: currentDeptId, deletedAt: null },
        select: { parentId: true },
      });

      if (!department) {
        return null;
      }

      // 如果是顶级部门（parentId = null），直接返回当前层级
      if (department.parentId === null) {
        return level;
      }

      // 向上追溯
      currentDeptId = department.parentId;
      level++;
    }

    // 超过最大深度，可能有数据问题
    this.logger.warn(`Department ${departmentId} exceeds maximum depth of ${maxDepth}`);
    return null;
  }

  /**
   * Validate rule configuration
   */
  private validateRuleConfig(ruleType: string, ruleConfig: any) {
    if (!ruleConfig) return;

    switch (ruleType) {
      case 'ORGANIZATION_RELATION':
        if (!ruleConfig.relation) {
          throw new BadRequestException(
            'ORGANIZATION_RELATION rule requires a "relation" field',
          );
        }
        break;

      case 'SYSTEM_ROLE_MAPPING':
        if (!ruleConfig.roleId && !ruleConfig.roleCode) {
          throw new BadRequestException(
            'SYSTEM_ROLE_MAPPING rule requires either "roleId" or "roleCode" field',
          );
        }
        break;

      case 'FIXED_USERS':
        // No validation needed
        break;

      case 'DYNAMIC_SCRIPT':
        if (!ruleConfig.script) {
          throw new BadRequestException(
            'DYNAMIC_SCRIPT rule requires a "script" field',
          );
        }
        break;

      default:
        throw new BadRequestException(`Unknown rule type: ${ruleType}`);
    }
  }
}
