import { and, desc, eq, inArray, or } from 'drizzle-orm';
import { employeeTrainingTargets, trainingPolicies, users } from '@/db/schema';
import { db } from '@/lib/db';
import { getEmployeeIdsForUser } from '@/features/employees/server/employee-identity-linking';

export type ResolvedTrainingTarget = {
  year: number;
  totalHours: number;
  kHours: number;
  sHours: number;
  aHours: number;
  source: 'employee' | 'policy' | 'default';
};

function normalizeNumber(value: number | null | undefined) {
  return Number((value ?? 0).toFixed(2));
}

function isMissingEmployeeTrainingTargetsStorage(error: unknown) {
  if (!(error instanceof Error)) {
    return false;
  }

  const message = error.message.toLowerCase();

  return (
    message.includes('employee_training_targets') &&
    (message.includes('does not exist') ||
      message.includes('relation') ||
      message.includes('column') ||
      message.includes('failed query'))
  );
}

async function getPolicySnapshotForYear(organizationId: string, year: number) {
  const [policy] = await db
    .select({
      year: trainingPolicies.year,
      totalHours: trainingPolicies.totalHours,
      kHours: trainingPolicies.kHours,
      sHours: trainingPolicies.sHours,
      aHours: trainingPolicies.aHours
    })
    .from(trainingPolicies)
    .where(and(eq(trainingPolicies.organizationId, organizationId), eq(trainingPolicies.year, year)))
    .orderBy(desc(trainingPolicies.updatedAt))
    .limit(1);

  if (policy) {
    return policy;
  }

  const [latestPolicy] = await db
    .select({
      year: trainingPolicies.year,
      totalHours: trainingPolicies.totalHours,
      kHours: trainingPolicies.kHours,
      sHours: trainingPolicies.sHours,
      aHours: trainingPolicies.aHours
    })
    .from(trainingPolicies)
    .where(eq(trainingPolicies.organizationId, organizationId))
    .orderBy(desc(trainingPolicies.year), desc(trainingPolicies.updatedAt))
    .limit(1);

  return (
    latestPolicy ?? {
      year,
      totalHours: 0,
      kHours: 0,
      sHours: 0,
      aHours: 0
    }
  );
}

export async function getResolvedTrainingTargetForUser({
  organizationId,
  userId,
  year
}: {
  organizationId: string;
  userId: string;
  year: number;
}): Promise<ResolvedTrainingTarget> {
  const employeeIds = await getEmployeeIdsForUser({ organizationId, userId });
  const [map, policy] = await Promise.all([
    getResolvedTrainingTargetsForEmployees({ organizationId, employeeIds, year }),
    getPolicySnapshotForYear(organizationId, year)
  ]);

  return (
    map.get(employeeIds[0] ?? -1) ?? {
      year: policy.year,
      totalHours: normalizeNumber(policy.totalHours),
      kHours: normalizeNumber(policy.kHours),
      sHours: normalizeNumber(policy.sHours),
      aHours: normalizeNumber(policy.aHours),
      source: policy.totalHours || policy.kHours || policy.sHours || policy.aHours ? 'policy' : 'default'
    }
  );
}

export async function getResolvedTrainingTargetForEmployee({
  organizationId,
  employeeId,
  year
}: {
  organizationId: string;
  employeeId: number;
  year: number;
}): Promise<ResolvedTrainingTarget> {
  const [map, policy] = await Promise.all([
    getResolvedTrainingTargetsForEmployees({ organizationId, employeeIds: [employeeId], year }),
    getPolicySnapshotForYear(organizationId, year)
  ]);

  return (
    map.get(employeeId) ?? {
      year: policy.year,
      totalHours: normalizeNumber(policy.totalHours),
      kHours: normalizeNumber(policy.kHours),
      sHours: normalizeNumber(policy.sHours),
      aHours: normalizeNumber(policy.aHours),
      source: policy.totalHours || policy.kHours || policy.sHours || policy.aHours ? 'policy' : 'default'
    }
  );
}

export async function getResolvedTrainingTargetsForEmployees({
  organizationId,
  employeeIds,
  year
}: {
  organizationId: string;
  employeeIds: number[];
  year: number;
}): Promise<Map<number, ResolvedTrainingTarget>> {
  const targetMap = new Map<number, ResolvedTrainingTarget>();

  if (employeeIds.length === 0) {
    return targetMap;
  }

  const policy = await getPolicySnapshotForYear(organizationId, year);
  let rows: Array<{
    employeeId: number | null;
    year: number;
    totalHours: number;
    kHours: number;
    sHours: number;
    aHours: number;
  }> = [];

  try {
    rows = await db
      .select({
        employeeId: employeeTrainingTargets.employeeId,
        year: employeeTrainingTargets.year,
        totalHours: employeeTrainingTargets.totalHours,
        kHours: employeeTrainingTargets.kHours,
        sHours: employeeTrainingTargets.sHours,
        aHours: employeeTrainingTargets.aHours
      })
      .from(employeeTrainingTargets)
      .where(
        and(
          eq(employeeTrainingTargets.organizationId, organizationId),
          eq(employeeTrainingTargets.year, year),
          inArray(employeeTrainingTargets.employeeId, employeeIds)
        )
      );
  } catch (error) {
    if (!isMissingEmployeeTrainingTargetsStorage(error)) {
      throw error;
    }
  }

  for (const row of rows) {
    if (row.employeeId === null) continue;

    targetMap.set(row.employeeId, {
      year: row.year,
      totalHours: normalizeNumber(row.totalHours),
      kHours: normalizeNumber(row.kHours),
      sHours: normalizeNumber(row.sHours),
      aHours: normalizeNumber(row.aHours),
      source: 'employee'
    });
  }

  for (const employeeId of employeeIds) {
    if (targetMap.has(employeeId)) continue;

    targetMap.set(employeeId, {
      year: policy.year,
      totalHours: normalizeNumber(policy.totalHours),
      kHours: normalizeNumber(policy.kHours),
      sHours: normalizeNumber(policy.sHours),
      aHours: normalizeNumber(policy.aHours),
      source: policy.totalHours || policy.kHours || policy.sHours || policy.aHours ? 'policy' : 'default'
    });
  }

  return targetMap;
}

export async function getResolvedTrainingTargetsForUsers({
  organizationId,
  userIds,
  year
}: {
  organizationId: string;
  userIds: string[];
  year: number;
}): Promise<Map<string, ResolvedTrainingTarget>> {
  const targetMap = new Map<string, ResolvedTrainingTarget>();

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

  const policy = await getPolicySnapshotForYear(organizationId, year);
  const userRows = await db
    .select({
      userId: users.id,
      employeeId: users.employeeId
    })
    .from(users)
    .where(inArray(users.id, userIds));
  const employeeIds = [...new Set(userRows.map((row) => row.employeeId).filter((value): value is number => value !== null))];
  let rows: Array<{
    userId: string | null;
    employeeId: number | null;
    year: number;
    totalHours: number;
    kHours: number;
    sHours: number;
    aHours: number;
  }> = [];

  try {
    rows = await db
      .select({
        userId: employeeTrainingTargets.userId,
        employeeId: employeeTrainingTargets.employeeId,
        year: employeeTrainingTargets.year,
        totalHours: employeeTrainingTargets.totalHours,
        kHours: employeeTrainingTargets.kHours,
        sHours: employeeTrainingTargets.sHours,
        aHours: employeeTrainingTargets.aHours
      })
      .from(employeeTrainingTargets)
      .where(
        and(
          eq(employeeTrainingTargets.organizationId, organizationId),
          eq(employeeTrainingTargets.year, year),
          or(
            inArray(employeeTrainingTargets.userId, userIds),
            ...(employeeIds.length > 0 ? [inArray(employeeTrainingTargets.employeeId, employeeIds)] : [])
          )!
        )
      );
  } catch (error) {
    if (!isMissingEmployeeTrainingTargetsStorage(error)) {
      throw error;
    }

    console.warn(
      'employee_training_targets storage is unavailable; falling back to training policy targets.',
      error
    );
  }

  const rowByUserId = new Map(rows.map((row) => [row.userId, row]));
  const rowByEmployeeId = new Map(
    rows
      .filter((row): row is typeof row & { employeeId: number } => row.employeeId !== null)
      .map((row) => [row.employeeId, row])
  );

  for (const userRow of userRows) {
    const row = rowByUserId.get(userRow.userId)
      ?? (userRow.employeeId !== null ? rowByEmployeeId.get(userRow.employeeId) : undefined);

    if (!row) {
      continue;
    }

    targetMap.set(userRow.userId, {
      year: row.year,
      totalHours: normalizeNumber(row.totalHours),
      kHours: normalizeNumber(row.kHours),
      sHours: normalizeNumber(row.sHours),
      aHours: normalizeNumber(row.aHours),
      source: 'employee'
    });
  }

  for (const userId of userIds) {
    if (targetMap.has(userId)) continue;

    targetMap.set(userId, {
      year: policy.year,
      totalHours: normalizeNumber(policy.totalHours),
      kHours: normalizeNumber(policy.kHours),
      sHours: normalizeNumber(policy.sHours),
      aHours: normalizeNumber(policy.aHours),
      source: policy.totalHours || policy.kHours || policy.sHours || policy.aHours ? 'policy' : 'default'
    });
  }

  return targetMap;
}
