import { and, asc, count, desc, eq, inArray, ilike, isNull, or, type SQL } from 'drizzle-orm';
import { msOptions } from '@/db/schema';
import { db } from '@/lib/db';
import { getCurrentOrganization } from '@/features/foundation/organization-context/service';
import type {
  MasterOptionListFilters,
  MasterOptionListResult,
  MasterOptionNode,
  MasterOptionRecord
} from './types';

async function resolveOrganizationId(organizationId?: string) {
  if (organizationId) {
    return organizationId;
  }

  const organization = await getCurrentOrganization();

  if (!organization) {
    throw new Error('Active organization is required');
  }

  return organization.id;
}

function parseSort(sort?: string) {
  if (!sort) {
    return desc(msOptions.updatedAt);
  }

  try {
    const parsed = JSON.parse(sort) as Array<{ id: string; desc?: boolean }>;
    const [rule] = parsed;

    if (!rule) {
      return desc(msOptions.updatedAt);
    }

    const sortMap = {
      category: msOptions.category,
      code: msOptions.code,
      name: msOptions.label,
      label: msOptions.label,
      sortOrder: msOptions.sortOrder,
      updatedAt: msOptions.updatedAt
    } as const;

    const column = sortMap[rule.id as keyof typeof sortMap];

    if (!column) {
      return desc(msOptions.updatedAt);
    }

    return rule.desc ? desc(column) : asc(column);
  } catch {
    return desc(msOptions.updatedAt);
  }
}

function buildFilters(organizationId: string, filters: MasterOptionListFilters): SQL[] {
  return [
    eq(msOptions.organizationId, organizationId),
    ...(filters.category ? [eq(msOptions.category, filters.category)] : []),
    ...(filters.activeOnly ? [eq(msOptions.isActive, true)] : []),
    ...(!filters.includeDeleted ? [isNull(msOptions.deletedAt)] : []),
    ...(filters.search
      ? [
          or(
            ilike(msOptions.label, `%${filters.search}%`),
            ilike(msOptions.code, `%${filters.search}%`),
            ilike(msOptions.category, `%${filters.search}%`)
          )!
        ]
      : [])
  ];
}

function mapMasterOption(
  row: typeof msOptions.$inferSelect,
  parentLabel: string | null
): MasterOptionRecord {
  return {
    id: row.id,
    organizationId: row.organizationId,
    category: row.category,
    code: row.code,
    label: row.label,
    value: row.value,
    parentId: row.parentId,
    parentLabel,
    sortOrder: row.sortOrder,
    isActive: row.isActive,
    metadata:
      row.metadata && typeof row.metadata === 'object'
        ? (row.metadata as Record<string, unknown>)
        : null,
    deletedAt: row.deletedAt ? row.deletedAt.toISOString() : null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString()
  };
}

export async function listMasterOptions(
  filters: MasterOptionListFilters = {}
): Promise<MasterOptionListResult> {
  const organizationId = await resolveOrganizationId(filters.organizationId);
  const page = filters.page ?? 1;
  const limit = filters.limit ?? 10;
  const whereFilters = buildFilters(organizationId, filters);
  const where = whereFilters.length === 1 ? whereFilters[0] : and(...whereFilters);
  const offset = (page - 1) * limit;

  const [totalResult] = await db.select({ value: count() }).from(msOptions).where(where);

  const rows = await db
    .select()
    .from(msOptions)
    .where(where)
    .orderBy(parseSort(filters.sort))
    .limit(limit)
    .offset(offset);

  const parentIds = [
    ...new Set(rows.map((row) => row.parentId).filter((value): value is string => Boolean(value)))
  ];
  const parentRows = parentIds.length
    ? await db
        .select()
        .from(msOptions)
        .where(and(eq(msOptions.organizationId, organizationId), inArray(msOptions.id, parentIds)))
    : [];
  const parentMap = new Map(parentRows.map((row) => [row.id, row.label]));

  return {
    items: rows.map((row) => mapMasterOption(row, parentMap.get(row.parentId ?? '') ?? null)),
    totalItems: totalResult?.value ?? 0
  };
}

export async function getOptionsByCategory(
  category: string,
  options?: {
    organizationId?: string;
    includeDeleted?: boolean;
    activeOnly?: boolean;
  }
): Promise<MasterOptionNode[]> {
  const organizationId = await resolveOrganizationId(options?.organizationId);
  const whereFilters = buildFilters(organizationId, {
    category,
    activeOnly: options?.activeOnly,
    includeDeleted: options?.includeDeleted,
    limit: Number.MAX_SAFE_INTEGER,
    page: 1
  });
  const where = whereFilters.length === 1 ? whereFilters[0] : and(...whereFilters);
  const rows = await db
    .select()
    .from(msOptions)
    .where(where)
    .orderBy(asc(msOptions.sortOrder), asc(msOptions.label));

  const parentMap = new Map(rows.map((row) => [row.id, row]));
  const itemMap = new Map<string, MasterOptionNode>();

  for (const row of rows) {
    const parent = row.parentId ? (parentMap.get(row.parentId) ?? null) : null;
    itemMap.set(
      row.id,
      Object.assign(mapMasterOption(row, parent?.label ?? null), {
        children: []
      })
    );
  }

  const roots: MasterOptionNode[] = [];

  for (const item of itemMap.values()) {
    if (item.parentId) {
      const parent = itemMap.get(item.parentId);

      if (parent) {
        parent.children.push(item);
        continue;
      }
    }

    roots.push(item);
  }

  return roots;
}

export async function getActiveOptionsByCategory(
  category: string,
  options?: { organizationId?: string }
) {
  return getOptionsByCategory(category, {
    organizationId: options?.organizationId,
    activeOnly: true
  });
}
