import { and, asc, count, desc, eq, ilike, or } from 'drizzle-orm';
import { departments, employees, organizations, positions } from '@/db/schema';
import { db } from '@/lib/db';
import type { EmployeeFilters, EmployeeMutationPayload } from '../api/types';

const employeeSelect = {
  id: employees.id,
  organizationId: employees.organizationId,
  employeeCode: employees.employeeCode,
  fullName: employees.fullName,
  email: employees.email,
  company: employees.company,
  departmentId: employees.departmentId,
  departmentName: departments.name,
  positionId: employees.positionId,
  positionName: positions.name,
  hiredAt: employees.hiredAt,
  isActive: employees.isActive,
  createdAt: employees.createdAt,
  updatedAt: employees.updatedAt
};

type EmployeeRow = {
  id: number;
  organizationId: string;
  employeeCode: string;
  fullName: string;
  email: string | null;
  company: string | null;
  departmentId: number | null;
  departmentName: string | null;
  positionId: number | null;
  positionName: string | null;
  hiredAt: Date | null;
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
};

const sortMap = {
  employee_code: employees.employeeCode,
  full_name: employees.fullName,
  department_name: departments.name,
  position_name: positions.name,
  company: employees.company,
  updated_at: employees.updatedAt
} as const;

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

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

  const values = statuses
    .split(/[.,]/)
    .map((value) => value.trim().toLowerCase())
    .filter(Boolean);

  const hasActive = values.includes('active');
  const hasInactive = values.includes('inactive');

  if (hasActive && !hasInactive) return eq(employees.isActive, true);
  if (hasInactive && !hasActive) return eq(employees.isActive, false);

  return null;
}

export function serializeEmployee(employee: EmployeeRow) {
  return {
    id: employee.id,
    organization_id: employee.organizationId,
    employee_code: employee.employeeCode,
    full_name: employee.fullName,
    email: employee.email,
    company: employee.company,
    department_id: employee.departmentId,
    department_name: employee.departmentName,
    position_id: employee.positionId,
    position_name: employee.positionName,
    hired_at: employee.hiredAt ? employee.hiredAt.toISOString() : null,
    is_active: employee.isActive,
    created_at: employee.createdAt.toISOString(),
    updated_at: employee.updatedAt.toISOString()
  };
}

export async function listEmployeesForOrganization({
  organizationId,
  filters
}: {
  organizationId: string;
  filters: EmployeeFilters;
}) {
  const page = Number(filters.page ?? 1);
  const limit = Number(filters.limit ?? 10);
  const search = filters.search?.trim();
  const sort = filters.sort;
  const statusFilter = toStatusFilter(filters.statuses);

  const clauses = [
    eq(employees.organizationId, organizationId),
    ...(search
      ? [
          or(
            ilike(employees.fullName, `%${search}%`),
            ilike(employees.employeeCode, `%${search}%`),
            ilike(employees.email, `%${search}%`),
            ilike(employees.company, `%${search}%`),
            ilike(departments.name, `%${search}%`),
            ilike(positions.name, `%${search}%`)
          )!
        ]
      : []),
    ...(statusFilter ? [statusFilter] : [])
  ];

  const where = clauses.length === 1 ? clauses[0] : and(...clauses);

  const baseQuery = db
    .select(employeeSelect)
    .from(employees)
    .leftJoin(departments, eq(employees.departmentId, departments.id))
    .leftJoin(positions, eq(employees.positionId, positions.id));

  const countQuery = db
    .select({ value: count() })
    .from(employees)
    .leftJoin(departments, eq(employees.departmentId, departments.id))
    .leftJoin(positions, eq(employees.positionId, positions.id))
    .where(where);

  let orderByClause = desc(employees.createdAt);

  if (sort) {
    try {
      const sortItems = JSON.parse(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 offset = (page - 1) * limit;
  const [totalResult] = await countQuery;
  const rows = await baseQuery.where(where).orderBy(orderByClause).limit(limit).offset(offset);

  return {
    offset,
    limit,
    total: totalResult?.value ?? 0,
    employees: rows.map((row) => serializeEmployee(row))
  };
}

export async function getEmployeeByIdForOrganization({
  id,
  organizationId
}: {
  id: number;
  organizationId: string;
}) {
  const [employee] = await db
    .select(employeeSelect)
    .from(employees)
    .leftJoin(departments, eq(employees.departmentId, departments.id))
    .leftJoin(positions, eq(employees.positionId, positions.id))
    .where(and(eq(employees.id, id), eq(employees.organizationId, organizationId)))
    .limit(1);

  return employee ?? null;
}

export async function resolveDepartmentId({
  organizationId,
  name
}: {
  organizationId: string;
  name?: string | null;
}) {
  const normalizedName = normalizeOptionalText(name);

  if (!normalizedName) return null;

  const existingDepartment = await db.query.departments.findFirst({
    where: and(eq(departments.organizationId, organizationId), eq(departments.name, normalizedName))
  });

  if (existingDepartment) {
    return existingDepartment.id;
  }

  const [department] = await db
    .insert(departments)
    .values({
      organizationId,
      name: normalizedName
    })
    .returning({ id: departments.id });

  return department.id;
}

export async function resolvePositionId({
  organizationId,
  name
}: {
  organizationId: string;
  name?: string | null;
}) {
  const normalizedName = normalizeOptionalText(name);

  if (!normalizedName) return null;

  const existingPosition = await db.query.positions.findFirst({
    where: and(eq(positions.organizationId, organizationId), eq(positions.name, normalizedName))
  });

  if (existingPosition) {
    return existingPosition.id;
  }

  const [position] = await db
    .insert(positions)
    .values({
      organizationId,
      name: normalizedName
    })
    .returning({ id: positions.id });

  return position.id;
}

export async function getEmployeeOptionsForOrganization(organizationId: string) {
  const rows = await db
    .select({
      id: employees.id,
      employeeCode: employees.employeeCode,
      fullName: employees.fullName
    })
    .from(employees)
    .where(and(eq(employees.organizationId, organizationId), eq(employees.isActive, true)))
    .orderBy(asc(employees.fullName), asc(employees.employeeCode));

  return rows.map((row) => ({
    value: String(row.id),
    label: `${row.fullName}${row.employeeCode ? ` (${row.employeeCode})` : ''}`
  }));
}

export async function createEmployeeForOrganization({
  organizationId,
  payload
}: {
  organizationId: string;
  payload: EmployeeMutationPayload;
}) {
  const normalized = normalizeEmployeePayload(payload);
  const organization = await db.query.organizations.findFirst({
    where: eq(organizations.id, organizationId),
    columns: {
      name: true
    }
  });

  if (!organization) {
    return null;
  }

  const departmentId = await resolveDepartmentId({
    organizationId,
    name: normalized.departmentName
  });
  const positionId = await resolvePositionId({
    organizationId,
    name: normalized.positionName
  });

  const [employee] = await db
    .insert(employees)
    .values({
      organizationId,
      employeeCode: normalized.employeeCode,
      fullName: normalized.fullName,
      email: normalized.email,
      company: organization.name,
      companyName: organization.name,
      departmentId,
      positionId,
      isActive: normalized.isActive
    })
    .returning({ id: employees.id });

  return getEmployeeByIdForOrganization({ id: employee.id, organizationId });
}

export async function updateEmployeeForOrganization({
  id,
  organizationId,
  payload
}: {
  id: number;
  organizationId: string;
  payload: EmployeeMutationPayload;
}) {
  const normalized = normalizeEmployeePayload(payload);
  const organization = await db.query.organizations.findFirst({
    where: eq(organizations.id, organizationId),
    columns: {
      name: true
    }
  });

  if (!organization) {
    return null;
  }

  const departmentId = await resolveDepartmentId({
    organizationId,
    name: normalized.departmentName
  });
  const positionId = await resolvePositionId({
    organizationId,
    name: normalized.positionName
  });

  await db
    .update(employees)
    .set({
      employeeCode: normalized.employeeCode,
      fullName: normalized.fullName,
      email: normalized.email,
      company: organization.name,
      companyName: organization.name,
      departmentId,
      positionId,
      isActive: normalized.isActive,
      updatedAt: new Date()
    })
    .where(and(eq(employees.id, id), eq(employees.organizationId, organizationId)));

  return getEmployeeByIdForOrganization({ id, organizationId });
}

export async function softDeleteEmployeeForOrganization({
  id,
  organizationId
}: {
  id: number;
  organizationId: string;
}) {
  await db
    .update(employees)
    .set({
      isActive: false,
      updatedAt: new Date()
    })
    .where(and(eq(employees.id, id), eq(employees.organizationId, organizationId)));
}

export function normalizeEmployeePayload(payload: EmployeeMutationPayload) {
  return {
    ...payload,
    email: normalizeOptionalText(payload.email),
    departmentName: normalizeOptionalText(payload.departmentName),
    positionName: normalizeOptionalText(payload.positionName)
  };
}
