import { getPrisma } from '../infrastructure/database/PrismaSingleton';
import { ConflictError, NotFoundError, ValidationError } from '../types/errors';

export class TeamManagementService {
  private prisma = getPrisma();

  private async resolveEmployeePk(businessEmployeeId: string) {
    const employee = await this.prisma.employees.findUnique({
      where: { employee_id: businessEmployeeId },
    });
    if (!employee || employee.status !== 'active') {
      throw new NotFoundError('Employee not found');
    }
    return employee;
  }

  private async requireActiveTeam(teamId: string) {
    const team = await this.prisma.l10_teams.findUnique({ where: { id: teamId } });
    if (!team || !team.active) throw new NotFoundError('Team not found');
    return team;
  }

  private async enrichMembers(teamId: string, memberRows: { employee_id: string; role: string; joined_at: Date }[]) {
    const pks = memberRows.map((m) => m.employee_id);
    const numericIds = pks.map((id) => parseInt(id, 10)).filter((n) => !Number.isNaN(n));
    const employees = await this.prisma.employees.findMany({
      where: {
        OR: [
          ...(numericIds.length ? [{ id: { in: numericIds } }] : []),
          ...(pks.length ? [{ employee_id: { in: pks } }] : []),
        ],
      },
    });

    const pkToEmployee = new Map<string, (typeof employees)[0]>();
    for (const e of employees) {
      pkToEmployee.set(String(e.id), e);
      pkToEmployee.set(e.employee_id, e);
    }

    const team = await this.prisma.l10_teams.findUnique({ where: { id: teamId } });
    const leadPk = team?.lead_employee_id ?? null;

    return memberRows
      .map((m) => {
        const emp = pkToEmployee.get(m.employee_id) ?? pkToEmployee.get(String(parseInt(m.employee_id, 10)));
        if (!emp) return null;
        const managerLevel = emp.is_manager ?? 0;
        // Hide resigned / non-working / pending-exit from team UI
        if (managerLevel === 3 || managerLevel === 4 || managerLevel === 5) return null;
        const isLead = leadPk === m.employee_id || m.role === 'lead';
        return {
          employeeId: emp.employee_id,
          name: emp.name,
          email: emp.email,
          role: m.role as 'lead' | 'member',
          managerLevel,
          isManager: managerLevel === 1,
          isAdmin: managerLevel === 2,
          isLead,
          joinedAt: m.joined_at.toISOString(),
        };
      })
      .filter((row): row is NonNullable<typeof row> => Boolean(row))
      .sort((a, b) => {
        if (a.isLead !== b.isLead) return a.isLead ? -1 : 1;
        if (a.isAdmin !== b.isAdmin) return a.isAdmin ? -1 : 1;
        if (a.isManager !== b.isManager) return a.isManager ? -1 : 1;
        return a.name.localeCompare(b.name);
      });
  }

  private async buildTeamDetail(teamId: string) {
    const team = await this.requireActiveTeam(teamId);
    const memberRows = await this.prisma.l10_team_members.findMany({
      where: { team_id: teamId },
      orderBy: { joined_at: 'asc' },
    });
    const members = await this.enrichMembers(teamId, memberRows);

    const leadMember = members.find((m) => m.isLead) ?? members[0] ?? null;

    return {
      teamId: team.id,
      teamName: team.name,
      description: team.description,
      leadEmployeeId: leadMember?.employeeId ?? null,
      leadName: leadMember?.name ?? null,
      memberCount: members.length,
      members,
    };
  }

  async listTeams() {
    const teams = await this.prisma.l10_teams.findMany({
      where: { active: true },
      orderBy: { name: 'asc' },
      include: { _count: { select: { l10_team_members: true } } },
    });

    const leadPks = teams.map((t) => t.lead_employee_id).filter((id): id is string => Boolean(id));
    const numericIds = leadPks.map((id) => parseInt(id, 10)).filter((n) => !Number.isNaN(n));
    const leadEmployees = leadPks.length
      ? await this.prisma.employees.findMany({
          where: {
            OR: [
              ...(numericIds.length ? [{ id: { in: numericIds } }] : []),
              { employee_id: { in: leadPks } },
            ],
          },
        })
      : [];

    const pkToBusiness = new Map<string, { employeeId: string; name: string }>();
    for (const e of leadEmployees) {
      pkToBusiness.set(String(e.id), { employeeId: e.employee_id, name: e.name });
      pkToBusiness.set(e.employee_id, { employeeId: e.employee_id, name: e.name });
    }

    return {
      items: teams.map((t) => {
        const lead =
          t.lead_employee_id != null
            ? pkToBusiness.get(t.lead_employee_id) ??
              pkToBusiness.get(String(parseInt(t.lead_employee_id, 10)))
            : null;
        return {
          teamId: t.id,
          teamName: t.name,
          description: t.description,
          leadEmployeeId: lead?.employeeId ?? null,
          leadName: lead?.name ?? null,
          memberCount: t._count.l10_team_members,
        };
      }),
    };
  }

  async getTeam(teamId: string) {
    return this.buildTeamDetail(teamId);
  }

  async addMember(teamId: string, businessEmployeeId: string, hrUsername: string) {
    await this.requireActiveTeam(teamId);
    const employee = await this.resolveEmployeePk(businessEmployeeId);
    const pk = String(employee.id);

    const existing = await this.prisma.l10_team_members.findUnique({
      where: { team_id_employee_id: { team_id: teamId, employee_id: pk } },
    });
    if (existing) throw new ConflictError('Employee is already on this team');

    await this.prisma.l10_team_members.create({
      data: { team_id: teamId, employee_id: pk, role: 'member' },
    });

    await this.prisma.l10_teams.update({
      where: { id: teamId },
      data: { updated_at: new Date() },
    });

    await this.prisma.leaves_audit_log.create({
      data: {
        actor_id: hrUsername,
        action: 'TEAM_MEMBER_ADD',
        entity: 'l10_team',
        entity_id: teamId,
        after_json: JSON.stringify({ employeeId: businessEmployeeId }),
      },
    });

    return this.buildTeamDetail(teamId);
  }

  async removeMember(teamId: string, businessEmployeeId: string, hrUsername: string) {
    const team = await this.requireActiveTeam(teamId);
    const employee = await this.resolveEmployeePk(businessEmployeeId);
    const pk = String(employee.id);

    const member = await this.prisma.l10_team_members.findUnique({
      where: { team_id_employee_id: { team_id: teamId, employee_id: pk } },
    });
    if (!member) throw new NotFoundError('Employee is not on this team');

    const memberCount = await this.prisma.l10_team_members.count({ where: { team_id: teamId } });
    if (memberCount <= 1) {
      throw new ValidationError('Cannot remove the last member from a team');
    }

    await this.prisma.l10_team_members.delete({
      where: { team_id_employee_id: { team_id: teamId, employee_id: pk } },
    });

    if (team.lead_employee_id === pk) {
      await this.prisma.l10_teams.update({
        where: { id: teamId },
        data: { lead_employee_id: null, updated_at: new Date() },
      });
    } else {
      await this.prisma.l10_teams.update({
        where: { id: teamId },
        data: { updated_at: new Date() },
      });
    }

    await this.prisma.leaves_audit_log.create({
      data: {
        actor_id: hrUsername,
        action: 'TEAM_MEMBER_REMOVE',
        entity: 'l10_team',
        entity_id: teamId,
        after_json: JSON.stringify({ employeeId: businessEmployeeId }),
      },
    });

    return this.buildTeamDetail(teamId);
  }

  async setLead(teamId: string, businessEmployeeId: string, hrUsername: string) {
    await this.requireActiveTeam(teamId);
    const employee = await this.resolveEmployeePk(businessEmployeeId);
    const pk = String(employee.id);

    const member = await this.prisma.l10_team_members.findUnique({
      where: { team_id_employee_id: { team_id: teamId, employee_id: pk } },
    });
    if (!member) throw new ValidationError('Team lead must be a member of the team');

    const team = await this.prisma.l10_teams.findUnique({ where: { id: teamId } });
    const previousLeadPk = team?.lead_employee_id;

    if (previousLeadPk && previousLeadPk !== pk) {
      await this.prisma.l10_team_members.updateMany({
        where: { team_id: teamId, employee_id: previousLeadPk, role: 'lead' },
        data: { role: 'member' },
      });
    }

    await this.prisma.l10_team_members.update({
      where: { team_id_employee_id: { team_id: teamId, employee_id: pk } },
      data: { role: 'lead' },
    });

    await this.prisma.l10_teams.update({
      where: { id: teamId },
      data: { lead_employee_id: pk, updated_at: new Date() },
    });

    await this.prisma.leaves_audit_log.create({
      data: {
        actor_id: hrUsername,
        action: 'TEAM_LEAD_SET',
        entity: 'l10_team',
        entity_id: teamId,
        after_json: JSON.stringify({ employeeId: businessEmployeeId }),
      },
    });

    return this.buildTeamDetail(teamId);
  }
}
