import { and, asc, eq, isNull } from 'drizzle-orm';
import { crmRoleProfiles } from '@/db/schema';
import { auditAction } from '@/features/foundation/audit-log/service';
import {
  CRM_PERMISSION_GROUPS,
  getDefaultRoleProfiles,
  isApprovalAuthority,
  isOwnershipScope,
  isScopeMode,
  type Permission
} from '@/lib/auth/rbac';
import { db } from '@/lib/db';
import { AuthError } from '@/lib/auth/session';
import type {
  CloneCrmRoleProfilePayload,
  CrmRoleProfileRecord,
  UpdateCrmRoleProfilePayload
} from '../api/types';

function mapRole(row: typeof crmRoleProfiles.$inferSelect): CrmRoleProfileRecord {
  return {
    id: row.id,
    organizationId: row.organizationId,
    code: row.code,
    name: row.name,
    description: row.description,
    permissions: row.permissions as Permission[],
    branchScopeMode: isScopeMode(row.branchScopeMode) ? row.branchScopeMode : 'assigned',
    productScopeMode: isScopeMode(row.productScopeMode) ? row.productScopeMode : 'assigned',
    ownershipScope: isOwnershipScope(row.ownershipScope) ? row.ownershipScope : 'own',
    approvalAuthority: isApprovalAuthority(row.approvalAuthority)
      ? row.approvalAuthority
      : 'none',
    isSystem: row.isSystem,
    isActive: row.isActive,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString()
  };
}

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

export async function ensureCrmRoleProfiles(
  organizationId: string,
  userId: string
) {
  const existingRows = await db
    .select({ code: crmRoleProfiles.code })
    .from(crmRoleProfiles)
    .where(
      and(
        eq(crmRoleProfiles.organizationId, organizationId),
        isNull(crmRoleProfiles.deletedAt)
      )
    );
  const existingCodes = new Set(existingRows.map((row) => row.code));
  const missingProfiles = getDefaultRoleProfiles().filter(
    (profile) => !existingCodes.has(profile.code)
  );

  if (!missingProfiles.length) {
    return;
  }

  await db.insert(crmRoleProfiles).values(
    missingProfiles.map((profile) => ({
      id: crypto.randomUUID(),
      organizationId,
      code: profile.code,
      name: profile.name,
      description: profile.description,
      permissions: profile.permissions,
      branchScopeMode: profile.branchScopeMode,
      productScopeMode: profile.productScopeMode,
      ownershipScope: profile.ownershipScope,
      approvalAuthority: profile.approvalAuthority,
      isSystem: profile.isSystem,
      isActive: true,
      createdBy: userId,
      updatedBy: userId
    }))
  );
}

export async function listCrmRoleProfiles(
  organizationId: string,
  userId: string
) {
  await ensureCrmRoleProfiles(organizationId, userId);

  const rows = await db
    .select()
    .from(crmRoleProfiles)
    .where(
      and(
        eq(crmRoleProfiles.organizationId, organizationId),
        isNull(crmRoleProfiles.deletedAt)
      )
    )
    .orderBy(asc(crmRoleProfiles.isSystem), asc(crmRoleProfiles.name));

  return {
    roles: rows.map(mapRole),
    permissionGroups: CRM_PERMISSION_GROUPS
  };
}

export async function getCrmRoleProfileByCode(
  organizationId: string,
  userId: string,
  code: string
) {
  await ensureCrmRoleProfiles(organizationId, userId);
  const row = await db.query.crmRoleProfiles.findFirst({
    where: and(
      eq(crmRoleProfiles.organizationId, organizationId),
      eq(crmRoleProfiles.code, code),
      isNull(crmRoleProfiles.deletedAt)
    )
  });

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

  return mapRole(row);
}

export async function updateCrmRoleProfile(
  organizationId: string,
  userId: string,
  code: string,
  payload: UpdateCrmRoleProfilePayload
) {
  const current = await db.query.crmRoleProfiles.findFirst({
    where: and(
      eq(crmRoleProfiles.organizationId, organizationId),
      eq(crmRoleProfiles.code, code),
      isNull(crmRoleProfiles.deletedAt)
    )
  });

  if (!current) {
    throw new AuthError('CRM role not found', 404);
  }

  const [updated] = await db
    .update(crmRoleProfiles)
    .set({
      name: payload.name.trim(),
      description: payload.description?.trim() || null,
      permissions: payload.permissions,
      branchScopeMode: payload.branchScopeMode,
      productScopeMode: payload.productScopeMode,
      ownershipScope: payload.ownershipScope,
      approvalAuthority: payload.approvalAuthority,
      isActive: payload.isActive,
      updatedAt: new Date(),
      updatedBy: userId
    })
    .where(eq(crmRoleProfiles.id, current.id))
    .returning();

  await auditAction({
    organizationId,
    userId,
    entityType: 'crm_role',
    entityId: updated.id,
    action: 'update',
    beforeData: mapRole(current),
    afterData: mapRole(updated)
  });

  return mapRole(updated);
}

export async function cloneCrmRoleProfile(
  organizationId: string,
  userId: string,
  sourceCode: string,
  payload: CloneCrmRoleProfilePayload
) {
  const source = await db.query.crmRoleProfiles.findFirst({
    where: and(
      eq(crmRoleProfiles.organizationId, organizationId),
      eq(crmRoleProfiles.code, sourceCode),
      isNull(crmRoleProfiles.deletedAt)
    )
  });

  if (!source) {
    throw new AuthError('Source CRM role not found', 404);
  }

  const targetCode = sanitizeCode(payload.code);

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

  const duplicate = await db.query.crmRoleProfiles.findFirst({
    where: and(
      eq(crmRoleProfiles.organizationId, organizationId),
      eq(crmRoleProfiles.code, targetCode),
      isNull(crmRoleProfiles.deletedAt)
    )
  });

  if (duplicate) {
    throw new AuthError('Role code already exists', 409);
  }

  const [created] = await db
    .insert(crmRoleProfiles)
    .values({
      id: crypto.randomUUID(),
      organizationId,
      code: targetCode,
      name: payload.name.trim(),
      description: payload.description?.trim() || source.description,
      permissions: source.permissions,
      branchScopeMode: source.branchScopeMode,
      productScopeMode: source.productScopeMode,
      ownershipScope: source.ownershipScope,
      approvalAuthority: source.approvalAuthority,
      isSystem: false,
      isActive: true,
      createdBy: userId,
      updatedBy: userId
    })
    .returning();

  await auditAction({
    organizationId,
    userId,
    entityType: 'crm_role',
    entityId: created.id,
    action: 'create',
    afterData: {
      sourceCode,
      role: mapRole(created)
    }
  });

  return mapRole(created);
}
