import { and, desc, eq, inArray, isNull } from 'drizzle-orm';
import { crmDocumentArtifacts, users } from '@/db/schema';
import { db } from '@/lib/db';
import { AuthError } from '@/lib/auth/session';
import { auditAction } from '@/features/foundation/audit-log/service';
import { getStorageProvider } from '@/features/foundation/storage/service';

export type DocumentArtifactRecord = {
  id: string;
  organizationId: string;
  entityType: string;
  entityId: string;
  documentType: string;
  artifactType: string;
  templateVersionId: string | null;
  storageProvider: string;
  storageKey: string;
  fileName: string;
  contentType: string;
  fileSize: number | null;
  checksum: string | null;
  status: string;
  generatedBy: string;
  generatedByName: string | null;
  generatedAt: string;
  lockedAt: string | null;
  lockedBy: string | null;
  lockedByName: string | null;
  voidedAt: string | null;
  voidedBy: string | null;
  voidReason: string | null;
  metadata: unknown | null;
  createdAt: string;
  updatedAt: string;
  deletedAt: string | null;
};

function mapDocumentArtifactRecord(
  row: typeof crmDocumentArtifacts.$inferSelect,
  names?: { generatedByName?: string | null; lockedByName?: string | null }
): DocumentArtifactRecord {
  return {
    id: row.id,
    organizationId: row.organizationId,
    entityType: row.entityType,
    entityId: row.entityId,
    documentType: row.documentType,
    artifactType: row.artifactType,
    templateVersionId: row.templateVersionId,
    storageProvider: row.storageProvider,
    storageKey: row.storageKey,
    fileName: row.fileName,
    contentType: row.contentType,
    fileSize: row.fileSize,
    checksum: row.checksum,
    status: row.status,
    generatedBy: row.generatedBy,
    generatedByName: names?.generatedByName ?? null,
    generatedAt: row.generatedAt.toISOString(),
    lockedAt: row.lockedAt?.toISOString() ?? null,
    lockedBy: row.lockedBy,
    lockedByName: names?.lockedByName ?? null,
    voidedAt: row.voidedAt?.toISOString() ?? null,
    voidedBy: row.voidedBy,
    voidReason: row.voidReason,
    metadata: row.metadata ?? null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
    deletedAt: row.deletedAt?.toISOString() ?? null
  };
}

async function enrichArtifact(row: typeof crmDocumentArtifacts.$inferSelect) {
  const userIds = [row.generatedBy, row.lockedBy].filter(Boolean) as string[];
  const userRows = userIds.length
    ? await db
        .select({ id: users.id, name: users.name })
        .from(users)
        .where(inArray(users.id, userIds))
    : [];
  const userMap = new Map(userRows.map((user) => [user.id, user.name]));

  return mapDocumentArtifactRecord(row, {
    generatedByName: userMap.get(row.generatedBy) ?? null,
    lockedByName: row.lockedBy ? (userMap.get(row.lockedBy) ?? null) : null
  });
}

export async function createArtifact(input: {
  organizationId: string;
  userId: string;
  entityType: string;
  entityId: string;
  documentType: string;
  artifactType: 'preview' | 'approved_pdf' | 'export';
  templateVersionId?: string | null;
  storageProvider: string;
  storageKey: string;
  fileName: string;
  contentType: string;
  fileSize?: number | null;
  checksum?: string | null;
  metadata?: unknown;
}) {
  const [created] = await db
    .insert(crmDocumentArtifacts)
    .values({
      id: crypto.randomUUID(),
      organizationId: input.organizationId,
      entityType: input.entityType,
      entityId: input.entityId,
      documentType: input.documentType,
      artifactType: input.artifactType,
      templateVersionId: input.templateVersionId ?? null,
      storageProvider: input.storageProvider,
      storageKey: input.storageKey,
      fileName: input.fileName,
      contentType: input.contentType,
      fileSize: input.fileSize ?? null,
      checksum: input.checksum ?? null,
      status: 'active',
      generatedBy: input.userId,
      generatedAt: new Date(),
      metadata: input.metadata ?? null
    })
    .returning();

  await auditAction({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'crm_document_artifact',
    entityId: created.id,
    action: 'create',
    afterData: created
  });

  return enrichArtifact(created);
}

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

  if (!artifact) {
    throw new AuthError('Document artifact not found', 404);
  }

  return artifact;
}

export async function getArtifact(id: string, organizationId: string) {
  return enrichArtifact(await getArtifactRow(id, organizationId));
}

export async function getActiveArtifactForEntity(input: {
  organizationId: string;
  entityType: string;
  entityId: string;
  artifactType: string;
}) {
  const artifacts = await db
    .select()
    .from(crmDocumentArtifacts)
    .where(
      and(
        eq(crmDocumentArtifacts.organizationId, input.organizationId),
        eq(crmDocumentArtifacts.entityType, input.entityType),
        eq(crmDocumentArtifacts.entityId, input.entityId),
        eq(crmDocumentArtifacts.artifactType, input.artifactType),
        isNull(crmDocumentArtifacts.deletedAt)
      )
    )
    .orderBy(desc(crmDocumentArtifacts.createdAt));

  const artifact = artifacts.find((row) => row.status === 'active' || row.status === 'locked');
  if (!artifact || artifact.status === 'voided' || artifact.status === 'deleted') {
    return null;
  }

  return enrichArtifact(artifact);
}

export async function lockArtifact(input: {
  artifactId: string;
  organizationId: string;
  userId: string;
}) {
  const row = await getArtifactRow(input.artifactId, input.organizationId);
  const [updated] = await db
    .update(crmDocumentArtifacts)
    .set({
      status: 'locked',
      lockedAt: row.lockedAt ?? new Date(),
      lockedBy: row.lockedBy ?? input.userId,
      updatedAt: new Date()
    })
    .where(eq(crmDocumentArtifacts.id, input.artifactId))
    .returning();

  await auditAction({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'crm_document_artifact',
    entityId: updated.id,
    action: 'lock',
    beforeData: row,
    afterData: updated
  });

  return enrichArtifact(updated);
}

export async function voidArtifact(input: {
  artifactId: string;
  organizationId: string;
  userId: string;
  reason: string;
}) {
  const row = await getArtifactRow(input.artifactId, input.organizationId);
  const [updated] = await db
    .update(crmDocumentArtifacts)
    .set({
      status: 'voided',
      voidedAt: new Date(),
      voidedBy: input.userId,
      voidReason: input.reason.trim(),
      updatedAt: new Date()
    })
    .where(eq(crmDocumentArtifacts.id, input.artifactId))
    .returning();

  await auditAction({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'crm_document_artifact',
    entityId: updated.id,
    action: 'void',
    beforeData: row,
    afterData: updated
  });

  return enrichArtifact(updated);
}

export async function deleteArtifactMetadataOnly(input: {
  artifactId: string;
  organizationId: string;
}) {
  await db
    .update(crmDocumentArtifacts)
    .set({
      status: 'deleted',
      deletedAt: new Date(),
      updatedAt: new Date()
    })
    .where(
      and(
        eq(crmDocumentArtifacts.id, input.artifactId),
        eq(crmDocumentArtifacts.organizationId, input.organizationId)
      )
    );
}

export async function downloadArtifact(input: {
  artifactId: string;
  organizationId: string;
  userId?: string;
  auditDownload?: boolean;
}) {
  const artifact = await getArtifactRow(input.artifactId, input.organizationId);

  if (artifact.status === 'voided' || artifact.status === 'deleted') {
    throw new AuthError('Document artifact is not available for download', 409);
  }

  const object = await getStorageProvider().getObject({
    key: artifact.storageKey
  });

  if (input.auditDownload && input.userId) {
    await auditAction({
      organizationId: input.organizationId,
      userId: input.userId,
      entityType: 'crm_document_artifact',
      entityId: artifact.id,
      action: 'download',
      afterData: {
        storageKey: artifact.storageKey,
        fileName: artifact.fileName
      }
    });
  }

  return {
    artifact: await enrichArtifact(artifact),
    object: {
      ...object,
      contentType: artifact.contentType,
      fileName: artifact.fileName,
      size: artifact.fileSize ?? object.size,
      checksum: artifact.checksum
    }
  };
}
