/**
 * Task 7 - handleDocumentCompleted auto-deposit.
 *
 * Verifies that when a document is completed:
 *  - The signed PDF is deposited into the owner's entity subfolder
 *    (files.folderId set + the matching entity FK set).
 *  - Owner is resolved via the Owner-wins chain:
 *      direct clientId / companyId / yachtId on the document, then
 *      interest.clientId / interest.yachtId when only interestId is set.
 *  - No owner → folderId=null, entity FKs null.
 *
 * Fixture convention: makePort + makeClient from helpers/factories;
 * TEST_USER_ID resolved once via beforeAll from a seeded user, matching
 * document-folders-crud.test.ts and document-folders-system-folders.test.ts.
 */

import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
import { and, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { documents, files, documentFolders } from '@/lib/db/schema/documents';
import { interests } from '@/lib/db/schema/interests';
import { user } from '@/lib/db/schema/users';
import { handleDocumentCompleted } from '@/lib/services/documents.service';
import { ensureSystemRoots } from '@/lib/services/document-folders.service';
import { makeClient, makeCompany, makePort, makeYacht } from '../helpers/factories';

// Stub Documenso download - do NOT hit the network.
vi.mock('@/lib/services/documenso-client', async (importOriginal) => {
  const real = await importOriginal<typeof import('@/lib/services/documenso-client')>();
  return {
    ...real,
    downloadSignedPdf: vi.fn(async () => Buffer.from('%PDF-1.4 stub\n')),
  };
});

// Stub storage backend - write to an in-memory map so no MinIO required.
const stubPuts = new Map<string, Buffer>();
vi.mock('@/lib/storage', async (importOriginal) => {
  const real = await importOriginal<typeof import('@/lib/storage')>();
  return {
    ...real,
    getStorageBackend: vi.fn(async () => ({
      put: async (path: string, data: Buffer) => {
        stubPuts.set(path, data);
      },
      get: async (path: string) => stubPuts.get(path) ?? Buffer.alloc(0),
      head: async (path: string) => {
        const buf = stubPuts.get(path);
        return buf ? { sizeBytes: buf.length, contentType: 'application/pdf' } : null;
      },
      delete: async (path: string) => {
        stubPuts.delete(path);
      },
      presignedGet: async () => 'http://stub-url',
      presignedPut: async () => ({ url: 'http://stub-url', fields: {} }),
    })),
  };
});

let TEST_USER_ID = '';

beforeAll(async () => {
  const [u] = await db.select({ id: user.id }).from(user).limit(1);
  if (!u) throw new Error('No user available; run pnpm db:seed first');
  TEST_USER_ID = u.id;
});

describe('handleDocumentCompleted · auto-deposit', () => {
  let portId: string;
  let clientId: string;

  beforeEach(async () => {
    stubPuts.clear();
    const port = await makePort();
    portId = port.id;

    // Ensure system roots exist (required for ensureEntityFolder to work).
    await db.delete(documentFolders).where(eq(documentFolders.portId, portId));
    await ensureSystemRoots(portId, TEST_USER_ID);

    const client = await makeClient({ portId });
    clientId = client.id;
  });

  it('client-direct: signed PDF lands in the client subfolder', async () => {
    const documensoId = `docu-auto-deposit-client-${Date.now()}`;
    const [doc] = await db
      .insert(documents)
      .values({
        portId,
        clientId,
        documentType: 'eoi',
        title: 'Auto-deposit test EOI (client)',
        status: 'partially_signed',
        documensoId,
        createdBy: 'seed',
      })
      .returning();

    await handleDocumentCompleted({ documentId: documensoId, portId });

    // Document should be marked completed.
    const updatedDoc = await db.query.documents.findFirst({
      where: eq(documents.id, doc!.id),
    });
    expect(updatedDoc?.status).toBe('completed');
    expect(updatedDoc?.signedFileId).not.toBeNull();

    // Signed file should have folderId set (the client entity subfolder).
    const fileRow = await db.query.files.findFirst({
      where: eq(files.id, updatedDoc!.signedFileId!),
    });
    expect(fileRow?.folderId).not.toBeNull();
    expect(fileRow?.clientId).toBe(clientId);

    // Verify the folder is the entity folder for this client.
    const folder = await db.query.documentFolders.findFirst({
      where: and(eq(documentFolders.entityType, 'client'), eq(documentFolders.entityId, clientId)),
    });
    expect(folder).toBeDefined();
    expect(fileRow?.folderId).toBe(folder!.id);
  });

  it('no owner: signed PDF lands at root with folder_id=null', async () => {
    const documensoId = `docu-auto-deposit-noowner-${Date.now()}`;
    const [doc] = await db
      .insert(documents)
      .values({
        portId,
        // No clientId, companyId, yachtId, or interestId.
        documentType: 'other',
        title: 'Auto-deposit test (no owner)',
        status: 'partially_signed',
        documensoId,
        createdBy: 'seed',
      })
      .returning();

    await handleDocumentCompleted({ documentId: documensoId, portId });

    const updatedDoc = await db.query.documents.findFirst({
      where: eq(documents.id, doc!.id),
    });
    expect(updatedDoc?.status).toBe('completed');
    expect(updatedDoc?.signedFileId).not.toBeNull();

    const fileRow = await db.query.files.findFirst({
      where: eq(files.id, updatedDoc!.signedFileId!),
    });
    expect(fileRow?.folderId).toBeNull();
    expect(fileRow?.clientId).toBeNull();
    expect(fileRow?.companyId).toBeNull();
    expect(fileRow?.yachtId).toBeNull();
  });

  it('via interest.clientId: resolves owner through linked interest', async () => {
    // Create an interest with clientId pointing to our client.
    const [interest] = await db
      .insert(interests)
      .values({
        portId,
        clientId,
        pipelineStage: 'eoi_sent',
      })
      .returning();

    const documensoId = `docu-auto-deposit-interest-${Date.now()}`;
    const [doc] = await db
      .insert(documents)
      .values({
        portId,
        interestId: interest!.id,
        // All direct owner FKs null - owner must be resolved via interest.
        documentType: 'eoi',
        title: 'Auto-deposit test EOI (via interest)',
        status: 'partially_signed',
        documensoId,
        createdBy: 'seed',
      })
      .returning();

    await handleDocumentCompleted({ documentId: documensoId, portId });

    const updatedDoc = await db.query.documents.findFirst({
      where: eq(documents.id, doc!.id),
    });
    expect(updatedDoc?.status).toBe('completed');
    expect(updatedDoc?.signedFileId).not.toBeNull();

    const fileRow = await db.query.files.findFirst({
      where: eq(files.id, updatedDoc!.signedFileId!),
    });
    // Should have been deposited with the interest's clientId.
    expect(fileRow?.clientId).toBe(clientId);
    expect(fileRow?.folderId).not.toBeNull();

    // And the folder should be the client entity folder.
    const folder = await db.query.documentFolders.findFirst({
      where: and(eq(documentFolders.entityType, 'client'), eq(documentFolders.entityId, clientId)),
    });
    expect(folder).toBeDefined();
    expect(fileRow?.folderId).toBe(folder!.id);
  });

  it('company-direct: signed PDF lands in the company subfolder', async () => {
    const company = await makeCompany({ portId });

    const documensoId = `docu-auto-deposit-company-${Date.now()}`;
    const [doc] = await db
      .insert(documents)
      .values({
        portId,
        companyId: company.id,
        documentType: 'other',
        title: 'Auto-deposit test (company direct)',
        status: 'partially_signed',
        documensoId,
        createdBy: 'seed',
      })
      .returning();

    await handleDocumentCompleted({ documentId: documensoId, portId });

    const updatedDoc = await db.query.documents.findFirst({
      where: eq(documents.id, doc!.id),
    });
    expect(updatedDoc?.status).toBe('completed');
    expect(updatedDoc?.signedFileId).not.toBeNull();

    const fileRow = await db.query.files.findFirst({
      where: eq(files.id, updatedDoc!.signedFileId!),
    });
    expect(fileRow?.companyId).toBe(company.id);
    expect(fileRow?.folderId).not.toBeNull();

    const folder = await db.query.documentFolders.findFirst({
      where: and(
        eq(documentFolders.entityType, 'company'),
        eq(documentFolders.entityId, company.id),
      ),
    });
    expect(folder).toBeDefined();
    expect(fileRow?.folderId).toBe(folder!.id);
  });

  it('yacht-direct: signed PDF lands in the yacht subfolder', async () => {
    // Yachts require a real owner client per schema constraint.
    const ownerClient = await makeClient({ portId });
    const yacht = await makeYacht({ portId, ownerType: 'client', ownerId: ownerClient.id });

    const documensoId = `docu-auto-deposit-yacht-${Date.now()}`;
    const [doc] = await db
      .insert(documents)
      .values({
        portId,
        yachtId: yacht.id,
        documentType: 'other',
        title: 'Auto-deposit test (yacht direct)',
        status: 'partially_signed',
        documensoId,
        createdBy: 'seed',
      })
      .returning();

    await handleDocumentCompleted({ documentId: documensoId, portId });

    const updatedDoc = await db.query.documents.findFirst({
      where: eq(documents.id, doc!.id),
    });
    expect(updatedDoc?.status).toBe('completed');
    expect(updatedDoc?.signedFileId).not.toBeNull();

    const fileRow = await db.query.files.findFirst({
      where: eq(files.id, updatedDoc!.signedFileId!),
    });
    expect(fileRow?.yachtId).toBe(yacht.id);
    expect(fileRow?.folderId).not.toBeNull();

    const folder = await db.query.documentFolders.findFirst({
      where: and(eq(documentFolders.entityType, 'yacht'), eq(documentFolders.entityId, yacht.id)),
    });
    expect(folder).toBeDefined();
    expect(fileRow?.folderId).toBe(folder!.id);
  });
});
