import { and, eq, isNull } from 'drizzle-orm';
import { crmRoleProfiles, crmUserRoleAssignments, memberships } from '@/db/schema';
import { ensureCrmRoleAssignmentsBackfillForMembership } from '@/features/foundation/crm-role-assignments/server/service';
import { db } from '@/lib/db';
import {
  ALL_PERMISSIONS,
  type CrmApprovalAuthority,
  type CrmEffectiveScopeMode,
  type CrmOwnershipScope,
  type CrmRoleAssignmentScopeMode,
  type CrmScopeMode,
  getDefaultBusinessRole,
  getDefaultPermissions,
  getMembershipBasePermissions,
  getRoleProfileDefinition,
  isApprovalAuthority,
  isMembershipRole,
  isOwnershipScope,
  isRoleAssignmentScopeMode,
  isScopeMode,
  type MembershipRole,
  type Permission,
  type SystemRole
} from './rbac';

export interface ResolvedCrmAccess {
  membershipRole: MembershipRole;
  businessRole: string;
  businessRoles: string[];
  permissions: Permission[];
  branchScopeIds: string[];
  productTypeScopeIds: string[];
  ownershipScope: CrmOwnershipScope;
  branchScopeMode: CrmEffectiveScopeMode;
  productScopeMode: CrmEffectiveScopeMode;
  approvalAuthority: CrmApprovalAuthority;
  roleProfileName: string | null;
}

type BranchScopedAccess = Pick<
  ResolvedCrmAccess,
  'branchScopeMode' | 'branchScopeIds'
> | {
  branchScopeMode: string;
  branchScopeIds: string[];
};

type ProductScopedAccess = Pick<
  ResolvedCrmAccess,
  'productScopeMode' | 'productTypeScopeIds'
> | {
  productScopeMode: string;
  productTypeScopeIds: string[];
};

function normalizeStringArray(value: unknown) {
  return Array.isArray(value)
    ? [...new Set(value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0))]
    : [];
}

const OWNERSHIP_SCOPE_RANK: Record<CrmOwnershipScope, number> = {
  monitor: 0,
  own: 1,
  team: 2,
  organization: 3
};

const APPROVAL_AUTHORITY_RANK: Record<CrmApprovalAuthority, number> = {
  none: 0,
  manager: 1,
  department: 2,
  final: 3
};

function getMostPermissiveOwnershipScope(scopes: CrmOwnershipScope[]) {
  return scopes.reduce<CrmOwnershipScope>(
    (current, scope) =>
      OWNERSHIP_SCOPE_RANK[scope] > OWNERSHIP_SCOPE_RANK[current] ? scope : current,
    'own'
  );
}

function getMostPermissiveApprovalAuthority(authorities: CrmApprovalAuthority[]) {
  return authorities.reduce<CrmApprovalAuthority>(
    (current, authority) =>
      APPROVAL_AUTHORITY_RANK[authority] > APPROVAL_AUTHORITY_RANK[current]
        ? authority
        : current,
    'none'
  );
}

function resolveEffectiveScopeFromAssignments(input: {
  assignmentMode: CrmRoleAssignmentScopeMode;
  assignmentIds: string[];
  profileMode: CrmScopeMode;
}) {
  if (input.assignmentMode === 'all') {
    return { mode: 'all' as const, ids: [] as string[] };
  }

  if (input.assignmentMode === 'none') {
    return { mode: 'none' as const, ids: [] as string[] };
  }

  if (input.assignmentMode === 'selected') {
    return input.assignmentIds.length
      ? { mode: 'assigned' as const, ids: input.assignmentIds }
      : { mode: 'none' as const, ids: [] as string[] };
  }

  if (input.profileMode === 'all') {
    return { mode: 'all' as const, ids: [] as string[] };
  }

  return input.assignmentIds.length
    ? { mode: 'assigned' as const, ids: input.assignmentIds }
    : { mode: 'none' as const, ids: [] as string[] };
}

function combineEffectiveScopes(
  scopes: Array<{ mode: CrmEffectiveScopeMode; ids: string[] }>
): { mode: CrmEffectiveScopeMode; ids: string[] } {
  if (scopes.some((scope) => scope.mode === 'all')) {
    return { mode: 'all', ids: [] };
  }

  const mergedIds = [...new Set(scopes.flatMap((scope) => scope.ids))];

  if (mergedIds.length > 0) {
    return {
      mode: 'assigned',
      ids: mergedIds
    };
  }

  return { mode: 'none', ids: [] };
}

function buildLegacyFallbackAccess(input: {
  systemRole: SystemRole;
  membership: typeof memberships.$inferSelect;
  membershipRole: MembershipRole;
  businessRole: string;
  roleProfile: typeof crmRoleProfiles.$inferSelect | undefined;
}) {
  const defaultDefinition = getRoleProfileDefinition(input.businessRole);
  const ownershipScope: CrmOwnershipScope = isOwnershipScope(input.roleProfile?.ownershipScope ?? '')
    ? (input.roleProfile!.ownershipScope as CrmOwnershipScope)
    : (defaultDefinition?.ownershipScope ?? 'own');
  const branchScopeMode =
    normalizeStringArray(input.membership.branchScopeIds).length > 0
      ? 'assigned'
      : (isScopeMode(input.roleProfile?.branchScopeMode ?? '')
          ? (input.roleProfile!.branchScopeMode as CrmScopeMode)
          : (defaultDefinition?.branchScopeMode ?? 'assigned'));
  const productScopeMode =
    normalizeStringArray(input.membership.productTypeScopeIds).length > 0
      ? 'assigned'
      : (isScopeMode(input.roleProfile?.productScopeMode ?? '')
          ? (input.roleProfile!.productScopeMode as CrmScopeMode)
          : (defaultDefinition?.productScopeMode ?? 'assigned'));
  const approvalAuthority: CrmApprovalAuthority = isApprovalAuthority(
    input.roleProfile?.approvalAuthority ?? ''
  )
    ? (input.roleProfile!.approvalAuthority as CrmApprovalAuthority)
    : (defaultDefinition?.approvalAuthority ?? 'none');
  const permissions =
    input.systemRole === 'super_admin'
      ? ALL_PERMISSIONS
      : [
          ...new Set([
            ...getDefaultPermissions(input.membershipRole, input.businessRole),
            ...(input.roleProfile?.permissions?.filter(
              (value): value is Permission => typeof value === 'string'
            ) ?? []),
            ...(input.membership.permissions?.filter(
              (value): value is Permission => typeof value === 'string'
            ) ?? [])
          ])
        ];

  return {
    membershipRole: input.membershipRole,
    businessRole: input.businessRole,
    businessRoles: [input.businessRole],
    permissions,
    branchScopeIds: normalizeStringArray(input.membership.branchScopeIds),
    productTypeScopeIds: normalizeStringArray(input.membership.productTypeScopeIds),
    ownershipScope,
    branchScopeMode:
      branchScopeMode === 'all'
        ? 'all'
        : normalizeStringArray(input.membership.branchScopeIds).length > 0
          ? 'assigned'
          : 'none',
    productScopeMode:
      productScopeMode === 'all'
        ? 'all'
        : normalizeStringArray(input.membership.productTypeScopeIds).length > 0
          ? 'assigned'
          : 'none',
    approvalAuthority,
    roleProfileName: input.roleProfile?.name ?? defaultDefinition?.name ?? null
  } satisfies ResolvedCrmAccess;
}

export async function resolveCrmMembershipAccess(input: {
  systemRole: SystemRole;
  organizationId: string;
  membership: typeof memberships.$inferSelect;
}): Promise<ResolvedCrmAccess> {
  if (input.systemRole === 'super_admin') {
    return {
      membershipRole: 'admin',
      businessRole: input.membership.businessRole,
      businessRoles: [input.membership.businessRole],
      permissions: ALL_PERMISSIONS,
      branchScopeIds: [],
      productTypeScopeIds: [],
      ownershipScope: 'organization',
      branchScopeMode: 'all',
      productScopeMode: 'all',
      approvalAuthority: 'final',
      roleProfileName: 'System Admin'
    };
  }

  const membershipRole = isMembershipRole(input.membership.role)
    ? input.membership.role
    : 'user';
  const businessRole = input.membership.businessRole || getDefaultBusinessRole(membershipRole);
  await ensureCrmRoleAssignmentsBackfillForMembership(
    input.organizationId,
    input.membership,
    input.membership.userId
  );

  const [roleProfile] = await db
    .select()
    .from(crmRoleProfiles)
    .where(
      and(
        eq(crmRoleProfiles.organizationId, input.organizationId),
        eq(crmRoleProfiles.code, businessRole),
        eq(crmRoleProfiles.isActive, true),
        isNull(crmRoleProfiles.deletedAt)
      )
    )
    .limit(1);
  const assignmentRows = await db
    .select({
      assignment: crmUserRoleAssignments,
      profileCode: crmRoleProfiles.code,
      profileName: crmRoleProfiles.name,
      profilePermissions: crmRoleProfiles.permissions,
      profileOwnershipScope: crmRoleProfiles.ownershipScope,
      profileBranchScopeMode: crmRoleProfiles.branchScopeMode,
      profileProductScopeMode: crmRoleProfiles.productScopeMode,
      profileApprovalAuthority: crmRoleProfiles.approvalAuthority
    })
    .from(crmUserRoleAssignments)
    .innerJoin(crmRoleProfiles, eq(crmRoleProfiles.id, crmUserRoleAssignments.roleProfileId))
    .where(
      and(
        eq(crmUserRoleAssignments.organizationId, input.organizationId),
        eq(crmUserRoleAssignments.userId, input.membership.userId),
        eq(crmUserRoleAssignments.isActive, true),
        isNull(crmUserRoleAssignments.deletedAt),
        isNull(crmRoleProfiles.deletedAt)
      )
    );

  if (!assignmentRows.length) {
    return buildLegacyFallbackAccess({
      systemRole: input.systemRole,
      membership: input.membership,
      membershipRole,
      businessRole,
      roleProfile
    });
  }

  const primaryAssignment =
    assignmentRows.find((row) => row.assignment.isPrimary) ?? assignmentRows[0];
  const permissions = [
    ...new Set([
      ...getMembershipBasePermissions(membershipRole),
      ...assignmentRows.flatMap((row) =>
        (row.profilePermissions ?? []).filter(
          (value): value is Permission => typeof value === 'string'
        )
      ),
      ...(input.membership.permissions?.filter(
        (value): value is Permission => typeof value === 'string'
      ) ?? [])
    ])
  ];
  const ownershipScopes = assignmentRows.map((row) =>
    isOwnershipScope(row.profileOwnershipScope)
      ? (row.profileOwnershipScope as CrmOwnershipScope)
      : 'own'
  );
  const approvalAuthorities = assignmentRows.map((row) =>
    isApprovalAuthority(row.profileApprovalAuthority)
      ? (row.profileApprovalAuthority as CrmApprovalAuthority)
      : 'none'
  );
  const branchScope = combineEffectiveScopes(
    assignmentRows.map((row) =>
      resolveEffectiveScopeFromAssignments({
        assignmentMode: isRoleAssignmentScopeMode(row.assignment.branchScopeMode)
          ? row.assignment.branchScopeMode
          : 'inherit',
        assignmentIds: normalizeStringArray(row.assignment.branchScopeIds),
        profileMode: isScopeMode(row.profileBranchScopeMode)
          ? (row.profileBranchScopeMode as CrmScopeMode)
          : 'assigned'
      })
    )
  );
  const productScope = combineEffectiveScopes(
    assignmentRows.map((row) =>
      resolveEffectiveScopeFromAssignments({
        assignmentMode: isRoleAssignmentScopeMode(row.assignment.productTypeScopeMode)
          ? row.assignment.productTypeScopeMode
          : 'inherit',
        assignmentIds: normalizeStringArray(row.assignment.productTypeScopeIds),
        profileMode: isScopeMode(row.profileProductScopeMode)
          ? (row.profileProductScopeMode as CrmScopeMode)
          : 'assigned'
      })
    )
  );

  return {
    membershipRole,
    businessRole: primaryAssignment?.profileCode ?? businessRole,
    businessRoles: assignmentRows.map((row) => row.profileCode),
    permissions,
    branchScopeIds: branchScope.ids,
    productTypeScopeIds: productScope.ids,
    ownershipScope: getMostPermissiveOwnershipScope(ownershipScopes),
    branchScopeMode: branchScope.mode,
    productScopeMode: productScope.mode,
    approvalAuthority: getMostPermissiveApprovalAuthority(approvalAuthorities),
    roleProfileName: primaryAssignment?.profileName ?? roleProfile?.name ?? null
  };
}

export function hasBranchScopeAccess(access: BranchScopedAccess, branchId?: string | null) {
  if (!branchId) {
    return true;
  }

  if (access.branchScopeMode === 'all') {
    return true;
  }

  if (access.branchScopeMode === 'none' || access.branchScopeIds.length === 0) {
    return false;
  }

  return access.branchScopeIds.includes(branchId);
}

export function hasProductScopeAccess(access: ProductScopedAccess, productTypeId?: string | null) {
  if (!productTypeId) {
    return true;
  }

  if (access.productScopeMode === 'all') {
    return true;
  }

  if (access.productScopeMode === 'none' || access.productTypeScopeIds.length === 0) {
    return false;
  }

  return access.productTypeScopeIds.includes(productTypeId);
}

export function hasCrmPermission(
  access: Pick<ResolvedCrmAccess, 'permissions'> | { permissions?: string[] | null },
  permission: string
) {
  return (access.permissions ?? []).includes(permission as Permission);
}

export function canViewQuotationPricing(
  access: Pick<ResolvedCrmAccess, 'permissions'> | { permissions?: string[] | null }
) {
  return hasCrmPermission(access, 'crm.quotation.pricing.read');
}
