import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";

import {
  PartialDocumentDeletionError,
  deleteDocumentSafely,
} from "../src/services/documentDeletionService.ts";

const target = {
  id: "document-test",
  tripId: "trip-test",
  storagePath: "trip-test/document-test.pdf",
};

function createDependencies({ storageError, databaseError } = {}) {
  const calls = [];

  return {
    calls,
    dependencies: {
      loadTarget: async () => {
        calls.push("load");
        return target;
      },
      deleteStorageFile: async () => {
        calls.push("storage");
        if (storageError) throw storageError;
      },
      deleteDatabaseRow: async () => {
        calls.push("database");
        if (databaseError) throw databaseError;
      },
      reloadDocuments: async () => {
        calls.push("reload");
      },
    },
  };
}

test("document deletion verifies access, deletes Storage, then deletes SQL", async () => {
  const { calls, dependencies } = createDependencies();

  const deleted = await deleteDocumentSafely(target.tripId, target.id, dependencies);

  assert.deepEqual(deleted, target);
  assert.deepEqual(calls, ["load", "storage", "database"]);
});

test("a Storage failure never attempts SQL deletion", async () => {
  const storageError = new Error("storage unavailable");
  const { calls, dependencies } = createDependencies({ storageError });

  await assert.rejects(
    deleteDocumentSafely(target.tripId, target.id, dependencies),
    storageError,
  );
  assert.deepEqual(calls, ["load", "storage"]);
});

test("a SQL failure after Storage deletion reloads state and reports a partial failure", async () => {
  const { calls, dependencies } = createDependencies({
    databaseError: new Error("database unavailable"),
  });

  await assert.rejects(
    deleteDocumentSafely(target.tripId, target.id, dependencies),
    PartialDocumentDeletionError,
  );
  assert.deepEqual(calls, ["load", "storage", "database", "reload"]);
});

test("trip and step document triggers each issue one operation and one final toast", async () => {
  const sources = await Promise.all([
    readFile(new URL("../src/components/dashboard/DocumentItem.tsx", import.meta.url), "utf8"),
    readFile(new URL("../src/components/dashboard/documents/DocumentItemSmall.tsx", import.meta.url), "utf8"),
  ]);

  for (const source of sources) {
    const deletionHandler = source.match(/const handleDelete = async \(\) => \{[\s\S]*?\n  \};/)?.[0];
    assert.ok(deletionHandler);
    assert.equal((deletionHandler.match(/await deleteDocument\(tripId, document\.id\)/g) ?? []).length, 1);
    assert.equal((deletionHandler.match(/toast\.success\(/g) ?? []).length, 1);
    assert.equal((deletionHandler.match(/toast\.error\(/g) ?? []).length, 1);
    assert.match(deletionHandler, /if \(deletionInProgress\.current\) return/);
    assert.match(deletionHandler, /deletionInProgress\.current = true/);
    assert.match(source, /disabled=\{isDeleting\}/);
    assert.doesNotMatch(source, /deleteStorageDocument/);
  }
});

test("the shared deletion data flow owns no notification", async () => {
  const sources = await Promise.all([
    readFile(new URL("../src/services/documentDeletionService.ts", import.meta.url), "utf8"),
    readFile(new URL("../src/hooks/useDocumentOperations.ts", import.meta.url), "utf8"),
    readFile(new URL("../src/hooks/useSupabaseDocuments.ts", import.meta.url), "utf8"),
    readFile(new URL("../src/hooks/useSupabaseStorage.ts", import.meta.url), "utf8"),
  ]);

  for (const source of sources) {
    assert.doesNotMatch(source, /from ["']sonner["']|toast\./);
  }
});
