import {
  and,
  asc,
  count,
  desc,
  eq,
  gte,
  ilike,
  inArray,
  lt,
  or,
  sql
} from 'drizzle-orm';
import type { Option } from '@/types/data-table';
import {
  certificates,
  departments,
  employeeTrainingTargets,
  employees,
  organizations,
  positions,
  trainingRecords
} from '@/db/schema';
import { db } from '@/lib/db';
import { normalizeTrainingTypeForUi } from '@/features/training-records/constants/training-record-options';
import { getResolvedTrainingTargetForEmployee } from '@/features/training-policy/server/employee-training-target-data';
import type {
  EmployeeDirectoryFilters,
  EmployeeDirectoryListItem,
  EmployeeTargetFormData,
  EmployeeTargetMutationPayload
} from '../api/types';

const employeeCompanyExpr = sql<string>`coalesce(${employees.company}, ${employees.companyName})`;
const approvedHoursExpr = sql<number>`coalesce(${trainingRecords.approvedHours}, ${trainingRecords.hours})`;

export type EmployeeDirectoryDetail = {
  user: {
    id: number;
    employeeCode: string | null;
    fullName: string;
    email: string | null;
    companyName: string | null;
    organizationName: string;
    departmentName: string | null;
    positionName: string | null;
    status: 'Active' | 'Inactive';
  };
  summary: {
    year: number;
    approvedHours: number;
    pendingHours: number;
    rejectedHours: number;
    totalHours: number;
    targetTotalHours: number;
    completionPercent: number;
  };
  ksaSummary: {
    approvedK: number;
    targetK: number;
    approvedS: number;
    targetS: number;
    approvedA: number;
    targetA: number;
  };
  records: Array<{
    id: number;
    trainingDate: string;
    courseName: string;
    trainingType: string;
    submittedHours: number;
    approvedHours: number | null;
    approvalStatus: 'draft' | 'pending' | 'approved' | 'rejected' | 'needs_revision';
    certificateCount: number;
  }>;
};

const sortMap = {
  employee_code: employees.employeeCode,
  full_name: employees.fullName,
  department_name: departments.name,
  position_name: positions.name,
  approved_hours: sql`coalesce(directory_summary.approved_hours, 0)`,
  pending_hours: sql`coalesce(directory_summary.pending_hours, 0)`,
  rejected_hours: sql`coalesce(directory_summary.rejected_hours, 0)`,
  total_hours: sql`coalesce(directory_summary.total_hours, 0)`
} as const;

const STATUS_OPTIONS: Option[] = [
  { value: 'active', label: 'ใช้งาน' },
  { value: 'inactive', label: 'ปิดใช้งาน' }
];

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

function toStatusLabel(isActive: boolean) {
  return isActive ? 'Active' : 'Inactive';
}

function toStatusFilter(status?: string | null) {
  if (!status) return null;

  const normalizedStatus = status.trim().toLowerCase();

  if (normalizedStatus === 'active') return eq(employees.isActive, true);
  if (normalizedStatus === 'inactive') return eq(employees.isActive, false);

  return null;
}

function buildEmployeeDirectoryWhere({
  organizationId,
  filters
}: {
  organizationId: string;
  filters: EmployeeDirectoryFilters;
}) {
  const search = filters.search?.trim();
  const statusFilter = toStatusFilter(filters.status);

  return and(
    eq(employees.organizationId, organizationId),
    ...(search
      ? [
          or(
            ilike(employees.employeeCode, `%${search}%`),
            ilike(employees.fullName, `%${search}%`),
            ilike(employees.email, `%${search}%`),
            ilike(employeeCompanyExpr, `%${search}%`),
            ilike(departments.name, `%${search}%`),
            ilike(positions.name, `%${search}%`)
          )!
        ]
      : []),
    ...(filters.company ? [eq(employeeCompanyExpr, filters.company)] : []),
    ...(filters.department ? [eq(departments.name, filters.department)] : []),
    ...(filters.position ? [eq(positions.name, filters.position)] : []),
    ...(statusFilter ? [statusFilter] : [])
  );
}

async function getEmployeeDirectoryOptions(organizationId: string) {
  const [companyRows, departmentRows, positionRows] = await Promise.all([
    db
      .select({
        label: employeeCompanyExpr
      })
      .from(employees)
      .where(and(eq(employees.organizationId, organizationId), eq(employees.isActive, true)))
      .groupBy(employeeCompanyExpr)
      .orderBy(asc(employeeCompanyExpr)),
    db
      .select({
        label: departments.name
      })
      .from(departments)
      .where(and(eq(departments.organizationId, organizationId), eq(departments.isActive, true)))
      .orderBy(asc(departments.name)),
    db
      .select({
        label: positions.name
      })
      .from(positions)
      .where(and(eq(positions.organizationId, organizationId), eq(positions.isActive, true)))
      .orderBy(asc(positions.name))
  ]);

  return {
    companies: companyRows
      .filter((row): row is { label: string } => Boolean(row.label))
      .map((row) => ({ value: row.label, label: row.label })),
    departments: departmentRows
      .filter((row): row is { label: string } => Boolean(row.label))
      .map((row) => ({ value: row.label, label: row.label })),
    positions: positionRows
      .filter((row): row is { label: string } => Boolean(row.label))
      .map((row) => ({ value: row.label, label: row.label })),
    statuses: STATUS_OPTIONS
  };
}

export async function listEmployeeDirectoryForOrganization({
  organizationId,
  filters
}: {
  organizationId: string;
  filters: EmployeeDirectoryFilters;
}) {
  const page = Number(filters.page ?? 1);
  const limit = Number(filters.limit ?? 10);
  const offset = (page - 1) * limit;
  const where = buildEmployeeDirectoryWhere({ organizationId, filters });

  const summaryQuery = db
    .select({
      employeeId: trainingRecords.employeeId,
      approvedHours: sql<number>`coalesce(sum(case when ${trainingRecords.approvalStatus} = 'approved' then ${approvedHoursExpr} else 0 end), 0)::float`.as(
        'approved_hours'
      ),
      pendingHours: sql<number>`coalesce(sum(case when ${trainingRecords.approvalStatus} in ('pending', 'needs_revision') then ${trainingRecords.hours} else 0 end), 0)::float`.as(
        'pending_hours'
      ),
      rejectedHours: sql<number>`coalesce(sum(case when ${trainingRecords.approvalStatus} = 'rejected' then ${trainingRecords.hours} else 0 end), 0)::float`.as(
        'rejected_hours'
      ),
      totalHours:
        sql<number>`coalesce(sum(case
          when ${trainingRecords.approvalStatus} = 'approved' then ${approvedHoursExpr}
          when ${trainingRecords.approvalStatus} in ('pending', 'needs_revision', 'rejected') then ${trainingRecords.hours}
          else 0
        end), 0)::float`.as('total_hours')
    })
    .from(trainingRecords)
    .where(eq(trainingRecords.organizationId, organizationId))
    .groupBy(trainingRecords.employeeId)
    .as('directory_summary');

  let orderByClause = desc(employees.createdAt);

  if (filters.sort) {
    try {
      const sortItems = JSON.parse(filters.sort) as Array<{
        id: keyof typeof sortMap;
        desc: boolean;
      }>;
      const primarySort = sortItems[0];

      if (primarySort?.id && sortMap[primarySort.id]) {
        orderByClause = primarySort.desc
          ? desc(sortMap[primarySort.id])
          : asc(sortMap[primarySort.id]);
      }
    } catch {
      orderByClause = desc(employees.createdAt);
    }
  }

  const countSubquery = db
    .select({
      id: employees.id
    })
    .from(employees)
    .leftJoin(departments, eq(employees.departmentId, departments.id))
    .leftJoin(positions, eq(employees.positionId, positions.id))
    .where(where)
    .as('scoped_employee_directory');

  const [options, totalResult, rows] = await Promise.all([
    getEmployeeDirectoryOptions(organizationId),
    db.select({ value: count() }).from(countSubquery),
    db
      .select({
        id: employees.id,
        employeeCode: employees.employeeCode,
        fullName: employees.fullName,
        email: employees.email,
        companyName: employeeCompanyExpr,
        departmentName: departments.name,
        positionName: positions.name,
        isActive: employees.isActive,
        approvedHours: summaryQuery.approvedHours,
        pendingHours: summaryQuery.pendingHours,
        rejectedHours: summaryQuery.rejectedHours,
        totalHours: summaryQuery.totalHours
      })
      .from(employees)
      .leftJoin(departments, eq(employees.departmentId, departments.id))
      .leftJoin(positions, eq(employees.positionId, positions.id))
      .leftJoin(summaryQuery, eq(summaryQuery.employeeId, employees.id))
      .where(where)
      .orderBy(orderByClause)
      .limit(limit)
      .offset(offset)
  ]);

  const employeesData: EmployeeDirectoryListItem[] = rows.map((row) => ({
    id: row.id,
    employee_code: row.employeeCode,
    full_name: row.fullName,
    email: row.email,
    company_name: row.companyName,
    department_name: row.departmentName,
    position_name: row.positionName,
    is_active: row.isActive,
    approved_hours: normalizeHours(row.approvedHours),
    pending_hours: normalizeHours(row.pendingHours),
    rejected_hours: normalizeHours(row.rejectedHours),
    total_hours: normalizeHours(row.totalHours)
  }));

  return {
    offset,
    limit,
    total: totalResult[0]?.value ?? 0,
    employees: employeesData,
    options
  };
}

export async function getEmployeeTargetForOrganization({
  organizationId,
  employeeId,
  year
}: {
  organizationId: string;
  employeeId: number;
  year: number;
}): Promise<EmployeeTargetFormData | null> {
  const [employee] = await db
    .select({
      id: employees.id,
      employeeCode: employees.employeeCode,
      fullName: employees.fullName
    })
    .from(employees)
    .where(and(eq(employees.organizationId, organizationId), eq(employees.id, employeeId)))
    .limit(1);

  if (!employee) {
    return null;
  }

  const [row, resolved] = await Promise.all([
    db
      .select({
        totalHours: employeeTrainingTargets.totalHours,
        kHours: employeeTrainingTargets.kHours,
        sHours: employeeTrainingTargets.sHours,
        aHours: employeeTrainingTargets.aHours
      })
      .from(employeeTrainingTargets)
      .where(
        and(
          eq(employeeTrainingTargets.organizationId, organizationId),
          eq(employeeTrainingTargets.employeeId, employeeId),
          eq(employeeTrainingTargets.year, year)
        )
      )
      .limit(1),
    getResolvedTrainingTargetForEmployee({
      organizationId,
      employeeId,
      year
    })
  ]);

  return {
    employeeId: employee.id,
    employeeName: employee.fullName,
    employeeCode: employee.employeeCode,
    year,
    totalHours: normalizeHours(row[0]?.totalHours ?? resolved.totalHours),
    kHours: normalizeHours(row[0]?.kHours ?? resolved.kHours),
    sHours: normalizeHours(row[0]?.sHours ?? resolved.sHours),
    aHours: normalizeHours(row[0]?.aHours ?? resolved.aHours),
    source: resolved.source
  };
}

export async function upsertEmployeeTargetForOrganization({
  organizationId,
  employeeId,
  actorUserId,
  values
}: {
  organizationId: string;
  employeeId: number;
  actorUserId: string;
  values: EmployeeTargetMutationPayload;
}) {
  const existing = await db.query.employeeTrainingTargets.findFirst({
    where: and(
      eq(employeeTrainingTargets.organizationId, organizationId),
      eq(employeeTrainingTargets.employeeId, employeeId),
      eq(employeeTrainingTargets.year, values.year)
    )
  });

  if (existing) {
    await db
      .update(employeeTrainingTargets)
      .set({
        totalHours: values.totalHours,
        kHours: values.kHours,
        sHours: values.sHours,
        aHours: values.aHours,
        updatedBy: actorUserId,
        updatedAt: new Date()
      })
      .where(eq(employeeTrainingTargets.id, existing.id));
  } else {
    await db.insert(employeeTrainingTargets).values({
      organizationId,
      employeeId,
      year: values.year,
      totalHours: values.totalHours,
      kHours: values.kHours,
      sHours: values.sHours,
      aHours: values.aHours,
      createdBy: actorUserId,
      updatedBy: actorUserId
    });
  }

  return getEmployeeTargetForOrganization({
    organizationId,
    employeeId,
    year: values.year
  });
}

export async function getEmployeeDirectoryDetail({
  organizationId,
  employeeId
}: {
  organizationId: string;
  employeeId: string;
}): Promise<EmployeeDirectoryDetail | null> {
  const numericEmployeeId = Number(employeeId);
  const [employeeRow] = await db
    .select({
      id: employees.id,
      employeeCode: employees.employeeCode,
      fullName: employees.fullName,
      email: employees.email,
      companyName: employeeCompanyExpr,
      organizationName: organizations.name,
      departmentName: departments.name,
      positionName: positions.name,
      isActive: employees.isActive
    })
    .from(employees)
    .innerJoin(organizations, eq(employees.organizationId, organizations.id))
    .leftJoin(departments, eq(employees.departmentId, departments.id))
    .leftJoin(positions, eq(employees.positionId, positions.id))
    .where(and(eq(employees.organizationId, organizationId), eq(employees.id, numericEmployeeId)))
    .limit(1);

  if (!employeeRow) {
    return null;
  }

  const year = new Date().getUTCFullYear();
  const yearStart = new Date(Date.UTC(year, 0, 1));
  const yearEnd = new Date(Date.UTC(year + 1, 0, 1));

  const [statusRows, categoryRows, recordRows, target] = await Promise.all([
    db
      .select({
        approvalStatus: trainingRecords.approvalStatus,
        totalHours:
          sql<number>`coalesce(sum(case when ${trainingRecords.approvalStatus} = 'approved' then ${approvedHoursExpr} else ${trainingRecords.hours} end), 0)::float`
      })
      .from(trainingRecords)
      .where(
        and(
          eq(trainingRecords.organizationId, organizationId),
          eq(trainingRecords.employeeId, numericEmployeeId),
          gte(trainingRecords.trainingDate, yearStart),
          lt(trainingRecords.trainingDate, yearEnd)
        )
      )
      .groupBy(trainingRecords.approvalStatus),
    db
      .select({
        category: trainingRecords.category,
        approvedHours: sql<number>`coalesce(sum(${approvedHoursExpr}), 0)::float`
      })
      .from(trainingRecords)
      .where(
        and(
          eq(trainingRecords.organizationId, organizationId),
          eq(trainingRecords.employeeId, numericEmployeeId),
          eq(trainingRecords.approvalStatus, 'approved'),
          gte(trainingRecords.trainingDate, yearStart),
          lt(trainingRecords.trainingDate, yearEnd)
        )
      )
      .groupBy(trainingRecords.category),
    db
      .select({
        id: trainingRecords.id,
        trainingDate: trainingRecords.trainingDate,
        courseName: trainingRecords.courseName,
        trainingType: trainingRecords.trainingType,
        submittedHours: trainingRecords.hours,
        approvedHours: trainingRecords.approvedHours,
        approvalStatus: trainingRecords.approvalStatus
      })
      .from(trainingRecords)
      .where(
        and(
          eq(trainingRecords.organizationId, organizationId),
          eq(trainingRecords.employeeId, numericEmployeeId)
        )
      )
      .orderBy(desc(trainingRecords.createdAt)),
    getResolvedTrainingTargetForEmployee({
      organizationId,
      employeeId: numericEmployeeId,
      year
    })
  ]);

  const certificateCountMap = new Map<number, number>();
  const recordIds = recordRows.map((row) => row.id);

  if (recordIds.length > 0) {
    const certificateRows = await db
      .select({
        trainingRecordId: certificates.trainingRecordId,
        value: sql<number>`count(*)::int`
      })
      .from(certificates)
      .where(
        and(
          eq(certificates.organizationId, organizationId),
          inArray(certificates.trainingRecordId, recordIds)
        )
      )
      .groupBy(certificates.trainingRecordId);

    for (const certificateRow of certificateRows) {
      certificateCountMap.set(certificateRow.trainingRecordId, Number(certificateRow.value ?? 0));
    }
  }

  const statusHours = {
    draft: 0,
    approved: 0,
    pending: 0,
    rejected: 0,
    needs_revision: 0
  };

  for (const row of statusRows) {
    statusHours[row.approvalStatus] = normalizeHours(row.totalHours);
  }

  const ksaValues = { K: 0, S: 0, A: 0 };

  for (const row of categoryRows) {
    if (row.category === 'K' || row.category === 'S' || row.category === 'A') {
      ksaValues[row.category] = normalizeHours(row.approvedHours);
    }
  }

  const approvedHours = normalizeHours(statusHours.approved);
  const pendingHours = normalizeHours(statusHours.pending + statusHours.needs_revision);
  const rejectedHours = normalizeHours(statusHours.rejected);
  const totalHours = normalizeHours(approvedHours + pendingHours + rejectedHours);
  const targetTotalHours = normalizeHours(target.totalHours);
  const completionPercent =
    targetTotalHours > 0 ? Math.min((approvedHours / targetTotalHours) * 100, 100) : 0;

  return {
    user: {
      id: employeeRow.id,
      employeeCode: employeeRow.employeeCode,
      fullName: employeeRow.fullName,
      email: employeeRow.email,
      companyName: employeeRow.companyName,
      organizationName: employeeRow.organizationName,
      departmentName: employeeRow.departmentName,
      positionName: employeeRow.positionName,
      status: toStatusLabel(employeeRow.isActive)
    },
    summary: {
      year,
      approvedHours,
      pendingHours,
      rejectedHours,
      totalHours,
      targetTotalHours,
      completionPercent: normalizeHours(completionPercent)
    },
    ksaSummary: {
      approvedK: ksaValues.K,
      targetK: normalizeHours(target.kHours),
      approvedS: ksaValues.S,
      targetS: normalizeHours(target.sHours),
      approvedA: ksaValues.A,
      targetA: normalizeHours(target.aHours)
    },
    records: recordRows.map((row) => ({
      id: row.id,
      trainingDate: row.trainingDate.toISOString(),
      courseName: row.courseName,
      trainingType: normalizeTrainingTypeForUi(row.trainingType),
      submittedHours: normalizeHours(row.submittedHours),
      approvedHours: row.approvedHours === null ? null : normalizeHours(row.approvedHours),
      approvalStatus: row.approvalStatus,
      certificateCount: certificateCountMap.get(row.id) ?? 0
    }))
  };
}
