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

function normalizeCode(code: string): string {
  return code
    .trim()
    .toLowerCase()
    .replace(/[\s-]+/g, '_')
    .replace(/[^a-z0-9_]/g, '')
    .replace(/_+/g, '_')
    .replace(/^_+|_+$/g, '');
}

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

  const organization = await getCurrentOrganization();
  if (!organization) {
    throw new Error('Active organization 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[] {
  const search = filters.search?.trim();

  return [
    eq(msOptions.organizationId, organizationId),
    ...(filters.category ? [eq(msOptions.category, filters.category)] : []),
    ...(filters.activeOnly ? [eq(msOptions.isActive, true)] : []),
    ...(!filters.includeDeleted ? [isNull(msOptions.deletedAt)] : []),
    ...(search
      ? [
          or(
            ilike(msOptions.label, `%${search}%`),
            ilike(msOptions.code, `%${search}%`),
            ilike(msOptions.category, `%${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 ?? null,
    parentId: row.parentId ?? null,
    parentLabel,
    sortOrder: row.sortOrder,
    isActive: row.isActive,
    metadata: (row.metadata as Record<string, unknown> | null) ?? null,
    deletedAt: row.deletedAt?.toISOString() ?? null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString()
  };
}

async function getParentLabelMap(
  organizationId: string,
  parentIds: string[]
): Promise<Map<string, string>> {
  if (parentIds.length === 0) {
    return new Map();
  }

  const parentRows = await db
    .select({
      id: msOptions.id,
      label: msOptions.label
    })
    .from(msOptions)
    .where(and(eq(msOptions.organizationId, organizationId), inArray(msOptions.id, parentIds)));

  return new Map(parentRows.map((row) => [row.id, row.label]));
}

async function assertOptionBelongsToOrganization(
  id: string,
  organizationId: string,
  options?: { includeDeleted?: boolean }
): Promise<typeof msOptions.$inferSelect> {
  const filters: SQL[] = [eq(msOptions.id, id), eq(msOptions.organizationId, organizationId)];
  if (!options?.includeDeleted) {
    filters.push(isNull(msOptions.deletedAt));
  }

  const [row] = await db
    .select()
    .from(msOptions)
    .where(and(...filters))
    .limit(1);

  if (!row) {
    throw new AuthError('Master option not found', 404);
  }

  return row;
}

async function assertParentOption(
  organizationId: string,
  parentId?: string | null,
  currentId?: string
): Promise<typeof msOptions.$inferSelect | null> {
  if (!parentId) {
    return null;
  }

  if (currentId && parentId === currentId) {
    throw new AuthError('Parent option cannot reference itself', 400);
  }

  const parent = await assertOptionBelongsToOrganization(parentId, organizationId);
  return parent;
}

async function assertUniqueCode(
  organizationId: string,
  category: string,
  code: string,
  currentId?: string
): Promise<void> {
  const filters: SQL[] = [
    eq(msOptions.organizationId, organizationId),
    eq(msOptions.category, category),
    eq(msOptions.code, code)
  ];

  if (currentId) {
    filters.push(ne(msOptions.id, currentId));
  }

  const [existing] = await db
    .select({ id: msOptions.id })
    .from(msOptions)
    .where(and(...filters))
    .limit(1);

  if (existing) {
    throw new AuthError('Duplicate option code in the same category', 409);
  }
}

function sanitizePayload(payload: MasterOptionMutationPayload) {
  const category = payload.category.trim();
  const code = normalizeCode(payload.code);
  const label = payload.label.trim();

  if (!category) {
    throw new AuthError('Category is required', 400);
  }

  if (!code) {
    throw new AuthError('Code is required', 400);
  }

  if (!label) {
    throw new AuthError('Label is required', 400);
  }

  return {
    category,
    code,
    label,
    value: payload.value?.trim() || null,
    parentId: payload.parentId?.trim() || null,
    sortOrder: payload.sortOrder ?? 0,
    isActive: payload.isActive ?? true,
    metadata: payload.metadata ?? null
  };
}

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 parentMap = await getParentLabelMap(organizationId, parentIds);

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

export async function getMasterOptionById(
  id: string,
  organizationId?: string,
  options?: { includeDeleted?: boolean }
): Promise<MasterOptionRecord> {
  const resolvedOrganizationId = await resolveOrganizationId(organizationId);
  const row = await assertOptionBelongsToOrganization(id, resolvedOrganizationId, options);
  const parentLabel =
    row.parentId && row.parentId !== row.id
      ? (await getParentLabelMap(resolvedOrganizationId, [row.parentId])).get(row.parentId) ?? null
      : null;

  return mapMasterOption(row, parentLabel);
}

export async function createMasterOption(
  organizationId: string,
  payload: MasterOptionMutationPayload
): Promise<MasterOptionRecord> {
  const values = sanitizePayload(payload);
  const parent = await assertParentOption(organizationId, values.parentId);
  await assertUniqueCode(organizationId, values.category, values.code);

  const [created] = await db
    .insert(msOptions)
    .values({
      id: crypto.randomUUID(),
      organizationId,
      category: values.category,
      code: values.code,
      label: values.label,
      value: values.value,
      parentId: parent?.id ?? null,
      sortOrder: values.sortOrder,
      isActive: values.isActive,
      metadata: values.metadata,
      updatedAt: new Date()
    })
    .returning();

  return mapMasterOption(created, parent?.label ?? null);
}

export async function updateMasterOption(
  id: string,
  organizationId: string,
  payload: Partial<MasterOptionMutationPayload>
): Promise<MasterOptionRecord> {
  const current = await assertOptionBelongsToOrganization(id, organizationId);
  const merged = sanitizePayload({
    category: payload.category ?? current.category,
    code: payload.code ?? current.code,
    label: payload.label ?? current.label,
    value: payload.value === undefined ? current.value : payload.value,
    parentId: payload.parentId === undefined ? current.parentId : payload.parentId,
    sortOrder: payload.sortOrder ?? current.sortOrder,
    isActive: payload.isActive ?? current.isActive,
    metadata: payload.metadata === undefined ? ((current.metadata as Record<string, unknown> | null) ?? null) : payload.metadata
  });

  const parent = await assertParentOption(organizationId, merged.parentId, id);
  await assertUniqueCode(organizationId, merged.category, merged.code, id);

  const [updated] = await db
    .update(msOptions)
    .set({
      category: merged.category,
      code: merged.code,
      label: merged.label,
      value: merged.value,
      parentId: parent?.id ?? null,
      sortOrder: merged.sortOrder,
      isActive: merged.isActive,
      metadata: merged.metadata,
      updatedAt: new Date()
    })
    .where(eq(msOptions.id, id))
    .returning();

  return mapMasterOption(updated, parent?.label ?? null);
}

export async function softDeleteMasterOption(
  id: string,
  organizationId: string
): Promise<MasterOptionRecord> {
  const current = await assertOptionBelongsToOrganization(id, organizationId);
  const now = new Date();

  const [updated] = await db
    .update(msOptions)
    .set({
      isActive: false,
      deletedAt: now,
      updatedAt: now
    })
    .where(eq(msOptions.id, id))
    .returning();

  const parentLabel =
    current.parentId && current.parentId !== current.id
      ? (await getParentLabelMap(organizationId, [current.parentId])).get(current.parentId) ?? null
      : null;

  return mapMasterOption(updated, parentLabel);
}

export async function listMasterOptionCategories(
  organizationId?: string
): Promise<MasterOptionCategorySummary[]> {
  const resolvedOrganizationId = await resolveOrganizationId(organizationId);
  const rows = await db
    .select()
    .from(msOptions)
    .where(and(eq(msOptions.organizationId, resolvedOrganizationId), isNull(msOptions.deletedAt)))
    .orderBy(asc(msOptions.category), asc(msOptions.sortOrder), asc(msOptions.label));

  const grouped = new Map<string, MasterOptionCategorySummary>();

  for (const row of rows) {
    const current = grouped.get(row.category) ?? {
      category: row.category,
      totalItems: 0,
      activeItems: 0
    };

    current.totalItems += 1;
    if (row.isActive) {
      current.activeItems += 1;
    }

    grouped.set(row.category, current);
  }

  return [...grouped.values()];
}

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
  });
  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, {
      ...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 }
): Promise<MasterOptionNode[]> {
  return getOptionsByCategory(category, {
    organizationId: options?.organizationId,
    activeOnly: true
  });
}
