import { and, asc, eq, inArray, isNull } from 'drizzle-orm';
import {
  employeeTrainingTargets,
  employees,
  memberships,
  trainingRecords,
  userEmployeeMaps,
  users
} from '@/db/schema';
import { db } from '@/lib/db';

function normalizeOptionalText(value?: string | null) {
  const normalized = value?.trim();
  return normalized ? normalized : null;
}

export function normalizeEmployeeCode(value?: string | null) {
  return normalizeOptionalText(value)?.toUpperCase() ?? null;
}

export async function findEmployeeByCodeForOrganization({
  organizationId,
  employeeCode
}: {
  organizationId: string;
  employeeCode?: string | null;
}) {
  const normalizedEmployeeCode = normalizeEmployeeCode(employeeCode);

  if (!normalizedEmployeeCode) {
    return null;
  }

  return db.query.employees.findFirst({
    where: and(
      eq(employees.organizationId, organizationId),
      eq(employees.employeeCode, normalizedEmployeeCode)
    )
  });
}

export async function upsertEmployeeMasterFromImport({
  organizationId,
  employeeCode,
  fullName,
  email,
  companyName,
  departmentId,
  positionId,
  isActive
}: {
  organizationId: string;
  employeeCode: string;
  fullName: string;
  email?: string | null;
  companyName?: string | null;
  departmentId: number | null;
  positionId: number | null;
  isActive: boolean;
}) {
  const normalizedEmployeeCode = normalizeEmployeeCode(employeeCode);

  if (!normalizedEmployeeCode) {
    throw new Error('EMPLOYEE_CODE_REQUIRED');
  }

  const displayName = fullName.trim();
  const existingEmployee = await findEmployeeByCodeForOrganization({
    organizationId,
    employeeCode: normalizedEmployeeCode
  });

  const values = {
    employeeCode: normalizedEmployeeCode,
    fullName: displayName,
    displayName,
    email: normalizeOptionalText(email),
    companyName: normalizeOptionalText(companyName),
    company: normalizeOptionalText(companyName),
    departmentId,
    positionId,
    isActive,
    status: isActive ? 'active' : 'inactive',
    updatedAt: new Date()
  } satisfies Partial<typeof employees.$inferInsert>;

  if (existingEmployee) {
    const [updatedEmployee] = await db
      .update(employees)
      .set(values)
      .where(eq(employees.id, existingEmployee.id))
      .returning();

    return updatedEmployee;
  }

  const [createdEmployee] = await db
    .insert(employees)
    .values({
      organizationId,
      employeeCode: normalizedEmployeeCode,
      fullName: displayName,
      displayName,
      email: normalizeOptionalText(email),
      companyName: normalizeOptionalText(companyName),
      company: normalizeOptionalText(companyName),
      departmentId,
      positionId,
      isActive,
      status: isActive ? 'active' : 'inactive'
    })
    .returning();

  return createdEmployee;
}

async function backfillLinkedEmployeeReferences({
  organizationId,
  userId,
  employeeId
}: {
  organizationId: string;
  userId: string;
  employeeId: number;
}) {
  await Promise.all([
    db
      .update(trainingRecords)
      .set({
        employeeId,
        userId,
        submittedByUserId: userId,
        updatedAt: new Date()
      })
      .where(
        and(
          eq(trainingRecords.organizationId, organizationId),
          eq(trainingRecords.userId, userId),
          isNull(trainingRecords.employeeId)
        )
      ),
    db
      .update(employeeTrainingTargets)
      .set({
        employeeId,
        userId,
        updatedAt: new Date()
      })
      .where(
        and(
          eq(employeeTrainingTargets.organizationId, organizationId),
          eq(employeeTrainingTargets.userId, userId),
          isNull(employeeTrainingTargets.employeeId)
        )
      )
  ]);
}

export async function syncUserEmployeeLink({
  organizationId,
  userId,
  employeeCode
}: {
  organizationId: string;
  userId: string;
  employeeCode?: string | null;
}) {
  const normalizedEmployeeCode = normalizeEmployeeCode(employeeCode);
  const employee = await findEmployeeByCodeForOrganization({
    organizationId,
    employeeCode: normalizedEmployeeCode
  });

  await db
    .update(users)
    .set({
      employeeCode: normalizedEmployeeCode,
      employeeId: employee?.id ?? null,
      updatedAt: new Date()
    })
    .where(eq(users.id, userId));

  if (employee) {
    await db
      .insert(userEmployeeMaps)
      .values({
        organizationId,
        userId,
        employeeId: employee.id,
        source: 'employee_code',
        isPrimary: true
      })
      .onConflictDoUpdate({
        target: [
          userEmployeeMaps.organizationId,
          userEmployeeMaps.userId,
          userEmployeeMaps.employeeId
        ],
        set: {
          isPrimary: true,
          source: 'employee_code',
          updatedAt: new Date()
        }
      });

    await backfillLinkedEmployeeReferences({
      organizationId,
      userId,
      employeeId: employee.id
    });
  }

  return employee;
}

export async function syncUsersForEmployeeCode({
  organizationId,
  employeeCode,
  employeeId
}: {
  organizationId: string;
  employeeCode: string;
  employeeId: number;
}) {
  const normalizedEmployeeCode = normalizeEmployeeCode(employeeCode);

  if (!normalizedEmployeeCode) {
    return [];
  }

  const linkedUsers = await db
    .select({ userId: users.id })
    .from(memberships)
    .innerJoin(users, eq(memberships.userId, users.id))
    .where(
      and(
        eq(memberships.organizationId, organizationId),
        eq(users.employeeCode, normalizedEmployeeCode)
      )
    );

  const userIds = linkedUsers.map((row) => row.userId);

  if (userIds.length === 0) {
    return [];
  }

  await db
    .update(users)
    .set({
      employeeId,
      employeeCode: normalizedEmployeeCode,
      updatedAt: new Date()
    })
    .where(inArray(users.id, userIds));

  await Promise.all(
    userIds.map((userId) =>
      db
        .insert(userEmployeeMaps)
        .values({
          organizationId,
          userId,
          employeeId,
          source: 'employee_import',
          isPrimary: true
        })
        .onConflictDoUpdate({
          target: [
            userEmployeeMaps.organizationId,
            userEmployeeMaps.userId,
            userEmployeeMaps.employeeId
          ],
          set: {
            isPrimary: true,
            source: 'employee_import',
            updatedAt: new Date()
          }
        })
    )
  );

  await Promise.all(
    userIds.map((userId) =>
      backfillLinkedEmployeeReferences({
        organizationId,
        userId,
        employeeId
      })
    )
  );

  return userIds;
}

export async function getEmployeeIdsForUser({
  organizationId,
  userId
}: {
  organizationId: string;
  userId: string;
}) {
  const mappedRows = await db
    .select({
      employeeId: userEmployeeMaps.employeeId
    })
    .from(userEmployeeMaps)
    .where(
      and(
        eq(userEmployeeMaps.organizationId, organizationId),
        eq(userEmployeeMaps.userId, userId)
      )
    )
    .orderBy(asc(userEmployeeMaps.id));

  const employeeIds = mappedRows.map((row) => row.employeeId);

  if (employeeIds.length > 0) {
    return [...new Set(employeeIds)];
  }

  const [fallbackUser] = await db
    .select({
      employeeId: users.employeeId
    })
    .from(users)
    .where(eq(users.id, userId))
    .limit(1);

  return fallbackUser?.employeeId ? [fallbackUser.employeeId] : [];
}

export async function getPrimaryEmployeeIdForUser({
  organizationId,
  userId
}: {
  organizationId: string;
  userId: string;
}) {
  const employeeIds = await getEmployeeIdsForUser({ organizationId, userId });
  return employeeIds[0] ?? null;
}
