import { and, asc, count, desc, eq, isNull, ne, or } from 'drizzle-orm';
import { crmApprovalMatrices, crmApprovalWorkflows, crmQuotations } from '@/db/schema';
import { auditAction } from '@/features/foundation/audit-log/service';
import { submitForApproval } from '@/features/foundation/approval/server/service';
import {
  type CrmSecurityContext,
  auditCrmSecurityEvent,
  canAccessScopedCrmRecord
} from '@/features/crm/security/server/service';
import { db } from '@/lib/db';
import { AuthError } from '@/lib/auth/session';
import type {
  CreateApprovalMatrixInput,
  UpdateApprovalMatrixInput
} from '../schemas/approval-matrix.schema';

export interface ApprovalMatrixRecord {
  id: string;
  organizationId: string;
  entityType: string;
  workflowId: string;
  branchId: string | null;
  productType: string | null;
  minAmount: number | null;
  maxAmount: number | null;
  currency: string | null;
  priority: number;
  isDefault: boolean;
  isActive: boolean;
  description: string | null;
  createdAt: string;
  updatedAt: string;
  deletedAt: string | null;
  createdBy: string;
  updatedBy: string;
}

export interface ResolveApprovalWorkflowInput {
  organizationId: string;
  entityType: 'quotation';
  branchId?: string | null;
  productType?: string | null;
  amount?: number | null;
  currency?: string | null;
}

export interface ApprovalWorkflowResolution {
  matrixId: string;
  workflowId: string;
  matchReason: string;
}

interface MatrixCandidate {
  row: typeof crmApprovalMatrices.$inferSelect;
  specificity: number;
  matchReason: string;
}

function mapApprovalMatrixRecord(row: typeof crmApprovalMatrices.$inferSelect): ApprovalMatrixRecord {
  return {
    id: row.id,
    organizationId: row.organizationId,
    entityType: row.entityType,
    workflowId: row.workflowId,
    branchId: row.branchId ?? null,
    productType: row.productType ?? null,
    minAmount: row.minAmount ?? null,
    maxAmount: row.maxAmount ?? null,
    currency: row.currency ?? null,
    priority: row.priority,
    isDefault: row.isDefault,
    isActive: row.isActive,
    description: row.description ?? null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
    deletedAt: row.deletedAt?.toISOString() ?? null,
    createdBy: row.createdBy,
    updatedBy: row.updatedBy
  };
}

async function assertWorkflowExists(workflowId: string, organizationId: string, entityType?: string) {
  const [workflow] = await db
    .select()
    .from(crmApprovalWorkflows)
    .where(
      and(
        eq(crmApprovalWorkflows.id, workflowId),
        eq(crmApprovalWorkflows.organizationId, organizationId),
        eq(crmApprovalWorkflows.isActive, true),
        isNull(crmApprovalWorkflows.deletedAt),
        ...(entityType ? [eq(crmApprovalWorkflows.entityType, entityType)] : [])
      )
    )
    .limit(1);

  if (!workflow) {
    throw new AuthError('Approval workflow not found', 404);
  }

  return workflow;
}

async function assertApprovalMatrix(id: string, organizationId: string) {
  const [matrix] = await db
    .select()
    .from(crmApprovalMatrices)
    .where(
      and(
        eq(crmApprovalMatrices.id, id),
        eq(crmApprovalMatrices.organizationId, organizationId),
        isNull(crmApprovalMatrices.deletedAt)
      )
    )
    .limit(1);

  if (!matrix) {
    throw new AuthError('Approval matrix not found', 404);
  }

  return matrix;
}

function buildMatchReason(parts: string[], row: typeof crmApprovalMatrices.$inferSelect) {
  const reasons = [...parts];
  if (row.isDefault) {
    reasons.push('default fallback');
  }
  reasons.push(`priority ${row.priority}`);
  return reasons.join(', ');
}

function buildCandidate(
  row: typeof crmApprovalMatrices.$inferSelect,
  input: ResolveApprovalWorkflowInput
): MatrixCandidate | null {
  if (!row.isActive || row.deletedAt) {
    return null;
  }

  if (row.productType && row.productType !== input.productType) {
    return null;
  }

  if (row.branchId && row.branchId !== input.branchId) {
    return null;
  }

  if (row.currency && row.currency !== input.currency) {
    return null;
  }

  if (row.minAmount !== null && row.minAmount !== undefined) {
    if (input.amount === null || input.amount === undefined || input.amount < row.minAmount) {
      return null;
    }
  }

  if (row.maxAmount !== null && row.maxAmount !== undefined) {
    if (input.amount === null || input.amount === undefined || input.amount > row.maxAmount) {
      return null;
    }
  }

  let specificity = row.isDefault ? -100 : 0;
  const reasons: string[] = [];

  if (row.productType) {
    specificity += 10;
    reasons.push('product type');
  }

  if (row.branchId) {
    specificity += 10;
    reasons.push('branch');
  }

  if (row.currency) {
    specificity += 5;
    reasons.push('currency');
  }

  if (row.minAmount !== null || row.maxAmount !== null) {
    specificity += 5;
    reasons.push('amount range');
  }

  return {
    row,
    specificity,
    matchReason: buildMatchReason(reasons, row)
  };
}

export async function listApprovalMatrices(organizationId: string): Promise<ApprovalMatrixRecord[]> {
  const rows = await db
    .select()
    .from(crmApprovalMatrices)
    .where(
      and(
        eq(crmApprovalMatrices.organizationId, organizationId),
        isNull(crmApprovalMatrices.deletedAt)
      )
    )
    .orderBy(
      asc(crmApprovalMatrices.entityType),
      asc(crmApprovalMatrices.priority),
      desc(crmApprovalMatrices.updatedAt)
    );

  return rows.map(mapApprovalMatrixRecord);
}

export async function getApprovalMatrix(id: string, organizationId: string): Promise<ApprovalMatrixRecord> {
  const matrix = await assertApprovalMatrix(id, organizationId);
  return mapApprovalMatrixRecord(matrix);
}

export async function createApprovalMatrix(
  organizationId: string,
  userId: string,
  payload: CreateApprovalMatrixInput
): Promise<ApprovalMatrixRecord> {
  await assertWorkflowExists(payload.workflowId, organizationId, payload.entityType);

  const [created] = await db
    .insert(crmApprovalMatrices)
    .values({
      id: crypto.randomUUID(),
      organizationId,
      entityType: payload.entityType.trim(),
      workflowId: payload.workflowId.trim(),
      branchId: payload.branchId,
      productType: payload.productType,
      minAmount: payload.minAmount,
      maxAmount: payload.maxAmount,
      currency: payload.currency,
      priority: payload.priority,
      isDefault: payload.isDefault,
      isActive: payload.isActive,
      description: payload.description,
      createdBy: userId,
      updatedBy: userId
    })
    .returning();

  return mapApprovalMatrixRecord(created);
}

export async function updateApprovalMatrix(
  id: string,
  organizationId: string,
  userId: string,
  payload: UpdateApprovalMatrixInput
): Promise<ApprovalMatrixRecord> {
  const current = await assertApprovalMatrix(id, organizationId);

  const nextEntityType = payload.entityType?.trim() ?? current.entityType;
  const nextWorkflowId = payload.workflowId?.trim() ?? current.workflowId;
  const nextMinAmount = payload.minAmount !== undefined ? payload.minAmount : current.minAmount;
  const nextMaxAmount = payload.maxAmount !== undefined ? payload.maxAmount : current.maxAmount;

  if (nextMinAmount !== null && nextMaxAmount !== null && nextMinAmount > nextMaxAmount) {
    throw new AuthError('minAmount must be less than or equal to maxAmount', 400);
  }

  await assertWorkflowExists(nextWorkflowId, organizationId, nextEntityType);

  const [updated] = await db
    .update(crmApprovalMatrices)
    .set({
      entityType: nextEntityType,
      workflowId: nextWorkflowId,
      branchId: payload.branchId !== undefined ? payload.branchId : current.branchId,
      productType: payload.productType !== undefined ? payload.productType : current.productType,
      minAmount: nextMinAmount,
      maxAmount: nextMaxAmount,
      currency: payload.currency !== undefined ? payload.currency : current.currency,
      priority: payload.priority ?? current.priority,
      isDefault: payload.isDefault ?? current.isDefault,
      isActive: payload.isActive ?? current.isActive,
      description: payload.description !== undefined ? payload.description : current.description,
      updatedAt: new Date(),
      updatedBy: userId
    })
    .where(eq(crmApprovalMatrices.id, id))
    .returning();

  return mapApprovalMatrixRecord(updated);
}

export async function softDeleteApprovalMatrix(
  id: string,
  organizationId: string,
  userId: string
): Promise<{ before: ApprovalMatrixRecord; after: ApprovalMatrixRecord }> {
  const current = await assertApprovalMatrix(id, organizationId);

  if (current.isDefault && current.isActive) {
    const [defaultCount] = await db
      .select({ value: count() })
      .from(crmApprovalMatrices)
      .where(
        and(
          eq(crmApprovalMatrices.organizationId, organizationId),
          eq(crmApprovalMatrices.entityType, current.entityType),
          eq(crmApprovalMatrices.isDefault, true),
          eq(crmApprovalMatrices.isActive, true),
          isNull(crmApprovalMatrices.deletedAt),
          ne(crmApprovalMatrices.id, id)
        )
      );

    if (!defaultCount || defaultCount.value === 0) {
      throw new AuthError('At least one active default matrix is required for this entity type', 400);
    }
  }

  const [updated] = await db
    .update(crmApprovalMatrices)
    .set({
      deletedAt: new Date(),
      updatedAt: new Date(),
      updatedBy: userId
    })
    .where(eq(crmApprovalMatrices.id, id))
    .returning();

  return {
    before: mapApprovalMatrixRecord(current),
    after: mapApprovalMatrixRecord(updated)
  };
}

export async function resolveApprovalWorkflowForEntity(
  input: ResolveApprovalWorkflowInput
): Promise<ApprovalWorkflowResolution> {
  const rows = await db
    .select()
    .from(crmApprovalMatrices)
    .where(
      and(
        eq(crmApprovalMatrices.organizationId, input.organizationId),
        eq(crmApprovalMatrices.entityType, input.entityType),
        eq(crmApprovalMatrices.isActive, true),
        isNull(crmApprovalMatrices.deletedAt),
        or(eq(crmApprovalMatrices.branchId, input.branchId ?? ''), isNull(crmApprovalMatrices.branchId)),
        or(
          eq(crmApprovalMatrices.productType, input.productType ?? ''),
          isNull(crmApprovalMatrices.productType)
        ),
        or(eq(crmApprovalMatrices.currency, input.currency ?? ''), isNull(crmApprovalMatrices.currency))
      )
    )
    .orderBy(desc(crmApprovalMatrices.createdAt), desc(crmApprovalMatrices.id));

  const candidates = rows
    .map((row) => buildCandidate(row, input))
    .filter((candidate): candidate is MatrixCandidate => candidate !== null);

  if (!candidates.length) {
    throw new AuthError('No approval matrix matched and no default matrix is configured', 400);
  }

  const specificCandidates = candidates.filter((candidate) => !candidate.row.isDefault);
  const defaultCandidates = candidates.filter((candidate) => candidate.row.isDefault);
  const workingSet = specificCandidates.length > 0 ? specificCandidates : defaultCandidates;

  if (!workingSet.length) {
    throw new AuthError('No default approval matrix is configured for this entity type', 400);
  }

  const ranked = [...workingSet].sort((left, right) => {
    if (left.specificity !== right.specificity) {
      return right.specificity - left.specificity;
    }

    if (left.row.priority !== right.row.priority) {
      return left.row.priority - right.row.priority;
    }

    const createdDiff = right.row.createdAt.getTime() - left.row.createdAt.getTime();
    if (createdDiff !== 0) {
      return createdDiff;
    }

    return right.row.id.localeCompare(left.row.id);
  });

  const [winner, runnerUp] = ranked;

  if (
    runnerUp &&
    winner.specificity === runnerUp.specificity &&
    winner.row.priority === runnerUp.row.priority &&
    winner.row.createdAt.getTime() === runnerUp.row.createdAt.getTime()
  ) {
    throw new AuthError(
      `Approval matrix conflict between ${winner.row.id} and ${runnerUp.row.id}`,
      409
    );
  }

  await assertWorkflowExists(winner.row.workflowId, input.organizationId, input.entityType);

  return {
    matrixId: winner.row.id,
    workflowId: winner.row.workflowId,
    matchReason: winner.matchReason
  };
}

export async function submitQuotationForApproval(
  input: {
    organizationId: string;
    userId: string;
    quotationId: string;
    remark?: string;
    accessContext: CrmSecurityContext;
  }
) {
  const [quotation] = await db
    .select()
    .from(crmQuotations)
    .where(
      and(
        eq(crmQuotations.id, input.quotationId),
        eq(crmQuotations.organizationId, input.organizationId),
        isNull(crmQuotations.deletedAt)
      )
    )
    .limit(1);

  if (!quotation) {
    throw new AuthError('Quotation not found', 404);
  }

  const canAccess = canAccessScopedCrmRecord(input.accessContext, {
    branchId: quotation.branchId,
    productType: quotation.quotationType,
    createdBy: quotation.createdBy,
    ownerUserId: quotation.salesmanId
  });

  if (!canAccess) {
    await auditCrmSecurityEvent({
      organizationId: input.organizationId,
      userId: input.userId,
      action: 'approval_scope_violation',
      entityType: 'crm_quotation',
      entityId: input.quotationId,
      reason: 'User attempted to submit approval outside resolved CRM scope',
      metadata: {
        branchId: quotation.branchId,
        productType: quotation.quotationType
      }
    });
    throw new AuthError('Forbidden', 403);
  }

  const resolution = await resolveApprovalWorkflowForEntity({
    organizationId: input.organizationId,
    entityType: 'quotation',
    branchId: quotation.branchId,
    productType: quotation.quotationType,
    amount: quotation.totalAmount,
    currency: quotation.currency
  });

  const request = await submitForApproval(input.organizationId, input.userId, {
    workflowId: resolution.workflowId,
    entityType: 'quotation',
    entityId: input.quotationId,
    remark: input.remark
  });

  await auditAction({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'crm_approval_matrix',
    entityId: resolution.matrixId,
    action: 'resolve_approval_matrix',
    afterData: {
      quotationId: input.quotationId,
      approvalRequestId: request.id,
      matrixId: resolution.matrixId,
      workflowId: resolution.workflowId,
      matchReason: resolution.matchReason,
      productType: quotation.quotationType,
      branchId: quotation.branchId,
      amount: quotation.totalAmount,
      currency: quotation.currency
    }
  });

  return { request, resolution };
}
