import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service';
import {
  CreateSkillEntryDto,
  UpdateSkillEntryDto,
  BulkSkillEntryDto,
} from './dto';
import { SkillLevel } from '@prisma/client';

/**
 * Mapping from numeric level to SkillLevel enum
 */
const SKILL_LEVEL_MAP: Record<number, SkillLevel> = {
  0: SkillLevel.NONE,
  1: SkillLevel.BEGINNER,
  2: SkillLevel.INTERMEDIATE,
  3: SkillLevel.ADVANCED,
  4: SkillLevel.TRAINER,
};

/**
 * Reverse mapping from SkillLevel enum to numeric level
 */
const SKILL_LEVEL_REVERSE_MAP: Record<SkillLevel, number> = {
  [SkillLevel.NONE]: 0,
  [SkillLevel.BEGINNER]: 1,
  [SkillLevel.INTERMEDIATE]: 2,
  [SkillLevel.ADVANCED]: 3,
  [SkillLevel.EXPERT]: 3, // Map EXPERT to 3 as well
  [SkillLevel.TRAINER]: 4,
};

/**
 * Interface for skill gap analysis result
 */
export interface SkillGapAnalysis {
  skillId: string;
  skillName: string;
  category: string | null;
  targetLevel: number;
  averageLevel: number;
  gap: number;
  employeesBelow: number;
  employeesAtOrAbove: number;
  totalEmployees: number;
}

/**
 * Interface for skill coverage result
 */
export interface SkillCoverage {
  skillId: string;
  skillName: string;
  levelDistribution: Record<number, number>;
  totalAssessed: number;
  coverage: number;
  averageLevel: number;
}

/**
 * Service for managing skill matrix entries (assessments)
 */
@Injectable()
export class SkillEntriesService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Get full skill matrix for a department
   */
  async getMatrixForDepartment(departmentId: string) {
    // Verify department exists
    const department = await this.prisma.department.findUnique({
      where: { id: departmentId },
      include: {
        users: {
          where: { isActive: true },
          include: {
            employee: true,
          },
        },
      },
    });

    if (!department) {
      throw new NotFoundException(`Department with ID ${departmentId} not found`);
    }

    // Get all relevant skills (department-specific + global)
    const skills = await this.prisma.skill.findMany({
      where: {
        isActive: true,
        OR: [{ departmentId: departmentId }, { departmentId: null }],
      },
      orderBy: [{ category: 'asc' }, { name: 'asc' }],
    });

    // Get all employees in the department
    const employees = await this.prisma.employee.findMany({
      where: {
        isActive: true,
        user: {
          departmentId: departmentId,
          isActive: true,
        },
      },
      include: {
        user: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
        skillMatrixEntries: {
          include: {
            skill: true,
            assessedBy: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
              },
            },
          },
        },
      },
      orderBy: {
        user: {
          lastName: 'asc',
        },
      },
    });

    // Build matrix data structure
    const matrix = employees.map((employee) => {
      const skillLevels: Record<string, {
        level: number;
        assessedAt: Date | null;
        assessedBy: { id: string; firstName: string; lastName: string } | null;
        notes: string | null;
      }> = {};

      for (const skill of skills) {
        const entry = employee.skillMatrixEntries.find(
          (e) => e.skillId === skill.id,
        );
        skillLevels[skill.id] = {
          level: entry ? SKILL_LEVEL_REVERSE_MAP[entry.level] : 0,
          assessedAt: entry?.assessedAt || null,
          assessedBy: entry?.assessedBy || null,
          notes: entry?.note || null,
        };
      }

      return {
        employee: {
          id: employee.id,
          employeeNumber: employee.employeeNumber,
          position: employee.position,
          user: employee.user,
        },
        skills: skillLevels,
      };
    });

    return {
      department: {
        id: department.id,
        name: department.name,
        code: department.code,
      },
      skills: skills.map((s) => ({
        id: s.id,
        name: s.name,
        description: s.description,
        category: s.category,
        isGlobal: s.departmentId === null,
      })),
      matrix,
    };
  }

  /**
   * Get skills for a single employee
   */
  async getEmployeeSkills(employeeId: string) {
    const employee = await this.prisma.employee.findUnique({
      where: { id: employeeId },
      include: {
        user: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
            departmentId: true,
            department: true,
          },
        },
        skillMatrixEntries: {
          include: {
            skill: {
              include: {
                department: {
                  select: {
                    id: true,
                    name: true,
                  },
                },
              },
            },
            assessedBy: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
              },
            },
          },
          orderBy: {
            skill: {
              name: 'asc',
            },
          },
        },
      },
    });

    if (!employee) {
      throw new NotFoundException(`Employee with ID ${employeeId} not found`);
    }

    // Transform entries with numeric levels
    const skills = employee.skillMatrixEntries.map((entry) => ({
      id: entry.id,
      skill: {
        id: entry.skill.id,
        name: entry.skill.name,
        description: entry.skill.description,
        category: entry.skill.category,
        department: entry.skill.department,
      },
      level: SKILL_LEVEL_REVERSE_MAP[entry.level],
      assessedAt: entry.assessedAt,
      assessedBy: entry.assessedBy,
      notes: entry.note,
    }));

    // Group by category
    const byCategory: Record<string, typeof skills> = {};
    for (const skill of skills) {
      const category = skill.skill.category || 'other';
      if (!byCategory[category]) {
        byCategory[category] = [];
      }
      byCategory[category].push(skill);
    }

    return {
      employee: {
        id: employee.id,
        employeeNumber: employee.employeeNumber,
        position: employee.position,
        user: employee.user,
      },
      skills,
      byCategory,
      summary: {
        total: skills.length,
        averageLevel:
          skills.length > 0
            ? skills.reduce((sum, s) => sum + s.level, 0) / skills.length
            : 0,
        levelDistribution: this.calculateLevelDistribution(skills.map((s) => s.level)),
      },
    };
  }

  /**
   * Create a new skill entry (assessment)
   */
  async create(createDto: CreateSkillEntryDto, assessedById: string) {
    // Verify employee exists
    const employee = await this.prisma.employee.findUnique({
      where: { id: createDto.employeeId },
    });

    if (!employee) {
      throw new NotFoundException(
        `Employee with ID ${createDto.employeeId} not found`,
      );
    }

    // Verify skill exists
    const skill = await this.prisma.skill.findUnique({
      where: { id: createDto.skillId },
    });

    if (!skill) {
      throw new NotFoundException(
        `Skill with ID ${createDto.skillId} not found`,
      );
    }

    // Check if entry already exists
    const existingEntry = await this.prisma.skillMatrixEntry.findUnique({
      where: {
        employeeId_skillId: {
          employeeId: createDto.employeeId,
          skillId: createDto.skillId,
        },
      },
    });

    if (existingEntry) {
      throw new BadRequestException(
        'Skill entry already exists. Use update endpoint instead.',
      );
    }

    return this.prisma.skillMatrixEntry.create({
      data: {
        employeeId: createDto.employeeId,
        skillId: createDto.skillId,
        level: SKILL_LEVEL_MAP[createDto.level],
        assessedById,
        note: createDto.notes,
        assessedAt: new Date(),
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                firstName: true,
                lastName: true,
              },
            },
          },
        },
        skill: true,
        assessedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
          },
        },
      },
    });
  }

  /**
   * Update an existing skill entry
   */
  async update(id: string, updateDto: UpdateSkillEntryDto, assessedById: string) {
    const entry = await this.prisma.skillMatrixEntry.findUnique({
      where: { id },
    });

    if (!entry) {
      throw new NotFoundException(`Skill entry with ID ${id} not found`);
    }

    const updateData: any = {
      assessedById,
      assessedAt: new Date(),
    };

    if (updateDto.level !== undefined) {
      updateData.level = SKILL_LEVEL_MAP[updateDto.level];
    }

    if (updateDto.notes !== undefined) {
      updateData.note = updateDto.notes;
    }

    return this.prisma.skillMatrixEntry.update({
      where: { id },
      data: updateData,
      include: {
        employee: {
          include: {
            user: {
              select: {
                firstName: true,
                lastName: true,
              },
            },
          },
        },
        skill: true,
        assessedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
          },
        },
      },
    });
  }

  /**
   * Delete a skill entry
   */
  async remove(id: string) {
    const entry = await this.prisma.skillMatrixEntry.findUnique({
      where: { id },
    });

    if (!entry) {
      throw new NotFoundException(`Skill entry with ID ${id} not found`);
    }

    return this.prisma.skillMatrixEntry.delete({
      where: { id },
    });
  }

  /**
   * Bulk create or update skill entries
   */
  async bulkUpsert(bulkDto: BulkSkillEntryDto, assessedById: string) {
    const results = [];

    for (const entry of bulkDto.entries) {
      const existingEntry = await this.prisma.skillMatrixEntry.findUnique({
        where: {
          employeeId_skillId: {
            employeeId: entry.employeeId,
            skillId: entry.skillId,
          },
        },
      });

      if (existingEntry) {
        // Update existing
        const updated = await this.prisma.skillMatrixEntry.update({
          where: { id: existingEntry.id },
          data: {
            level: SKILL_LEVEL_MAP[entry.level],
            note: entry.notes,
            assessedById,
            assessedAt: new Date(),
          },
        });
        results.push({ action: 'updated', entry: updated });
      } else {
        // Create new
        const created = await this.prisma.skillMatrixEntry.create({
          data: {
            employeeId: entry.employeeId,
            skillId: entry.skillId,
            level: SKILL_LEVEL_MAP[entry.level],
            note: entry.notes,
            assessedById,
            assessedAt: new Date(),
          },
        });
        results.push({ action: 'created', entry: created });
      }
    }

    return {
      processed: results.length,
      created: results.filter((r) => r.action === 'created').length,
      updated: results.filter((r) => r.action === 'updated').length,
      results,
    };
  }

  /**
   * Analyze skill gaps for a department
   */
  async analyzeGaps(departmentId: string, targetLevel: number = 2): Promise<SkillGapAnalysis[]> {
    // Verify department exists
    const department = await this.prisma.department.findUnique({
      where: { id: departmentId },
    });

    if (!department) {
      throw new NotFoundException(`Department with ID ${departmentId} not found`);
    }

    // Get all skills for the department
    const skills = await this.prisma.skill.findMany({
      where: {
        isActive: true,
        OR: [{ departmentId: departmentId }, { departmentId: null }],
      },
    });

    // Get all employees in the department
    const employees = await this.prisma.employee.findMany({
      where: {
        isActive: true,
        user: {
          departmentId: departmentId,
          isActive: true,
        },
      },
    });

    const totalEmployees = employees.length;

    // Analyze each skill
    const analysis: SkillGapAnalysis[] = [];

    for (const skill of skills) {
      const entries = await this.prisma.skillMatrixEntry.findMany({
        where: {
          skillId: skill.id,
          employeeId: { in: employees.map((e) => e.id) },
        },
      });

      const levels = entries.map((e) => SKILL_LEVEL_REVERSE_MAP[e.level]);
      const averageLevel =
        levels.length > 0
          ? levels.reduce((sum, l) => sum + l, 0) / levels.length
          : 0;

      const employeesBelow = levels.filter((l) => l < targetLevel).length;
      const employeesAtOrAbove = levels.filter((l) => l >= targetLevel).length;

      analysis.push({
        skillId: skill.id,
        skillName: skill.name,
        category: skill.category,
        targetLevel,
        averageLevel: Math.round(averageLevel * 100) / 100,
        gap: Math.round((targetLevel - averageLevel) * 100) / 100,
        employeesBelow,
        employeesAtOrAbove,
        totalEmployees,
      });
    }

    // Sort by gap (largest first)
    return analysis.sort((a, b) => b.gap - a.gap);
  }

  /**
   * Get skill coverage analysis
   */
  async getSkillCoverage(skillId: string): Promise<SkillCoverage> {
    const skill = await this.prisma.skill.findUnique({
      where: { id: skillId },
    });

    if (!skill) {
      throw new NotFoundException(`Skill with ID ${skillId} not found`);
    }

    const entries = await this.prisma.skillMatrixEntry.findMany({
      where: { skillId },
      include: {
        employee: {
          include: {
            user: true,
          },
        },
      },
    });

    const levels = entries.map((e) => SKILL_LEVEL_REVERSE_MAP[e.level]);
    const levelDistribution = this.calculateLevelDistribution(levels);

    // Calculate total potential employees (all active employees)
    const totalEmployees = await this.prisma.employee.count({
      where: { isActive: true },
    });

    return {
      skillId: skill.id,
      skillName: skill.name,
      levelDistribution,
      totalAssessed: entries.length,
      coverage:
        totalEmployees > 0
          ? Math.round((entries.length / totalEmployees) * 100)
          : 0,
      averageLevel:
        levels.length > 0
          ? Math.round(
              (levels.reduce((sum, l) => sum + l, 0) / levels.length) * 100,
            ) / 100
          : 0,
    };
  }

  /**
   * Get potential trainers for a skill (level 4)
   */
  async getTrainers(skillId: string) {
    const skill = await this.prisma.skill.findUnique({
      where: { id: skillId },
    });

    if (!skill) {
      throw new NotFoundException(`Skill with ID ${skillId} not found`);
    }

    const trainers = await this.prisma.skillMatrixEntry.findMany({
      where: {
        skillId,
        level: SkillLevel.TRAINER,
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
                department: {
                  select: {
                    id: true,
                    name: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    return {
      skill: {
        id: skill.id,
        name: skill.name,
        category: skill.category,
      },
      trainers: trainers.map((t) => ({
        employeeId: t.employee.id,
        employeeNumber: t.employee.employeeNumber,
        position: t.employee.position,
        user: t.employee.user,
        assessedAt: t.assessedAt,
      })),
      totalTrainers: trainers.length,
    };
  }

  /**
   * Helper: Calculate level distribution
   */
  private calculateLevelDistribution(levels: number[]): Record<number, number> {
    const distribution: Record<number, number> = {
      0: 0,
      1: 0,
      2: 0,
      3: 0,
      4: 0,
    };

    for (const level of levels) {
      if (distribution[level] !== undefined) {
        distribution[level]++;
      }
    }

    return distribution;
  }
}
