import { testClient } from "hono/testing";
import { describe, expect, it } from "vitest";
import { createConfig } from "../../config.js";
import { MiddlewareManager } from "../../middlewares/middleware.js";
import { getMainRouter } from "../../routers/index.js";
import { getTestUser } from "../helpers/dbSetup.js";

interface Note {
  id: string;
  title: string;
  content: string;
  user_id: string;
  created_at: string;
  updated_at: string;
  deleted_at: string | null;
}

describe("Notes Endpoint", () => {
  // In test mode, createConfig() reads from .env.test
  const config = createConfig();

  // Try to initialize MiddlewareManager, but if it's already initialized, that's fine
  try {
    MiddlewareManager.initialize(config);
  } catch {
    // Already initialized, continue
  }

  const app = getMainRouter(config);
  // biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
  const client = testClient(app) as any;

  const ownerUser = getTestUser("owner");
  const temporaryUser = getTestUser("temp");

  // Helper function to get notes for a tablo
  const getTabloNotesResponse = async (
    // biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
    client: any,
    tabloId: string,
    accessToken?: string
  ) => {
    if (accessToken) {
      return await client.notes[":tabloId"].$get(
        { param: { tabloId } },
        { headers: { Authorization: `Bearer ${accessToken}` } }
      );
    }

    return await client.notes[":tabloId"].$get({ param: { tabloId } });
  };

  describe("GET /notes/:tabloId - Owner's Tablo Notes", () => {
    it("should return notes for owner's private tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_private",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);
    });

    it("should return shared notes for owner's shared tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_shared",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);

      // Should include test_note_2 and test_note_4 (shared in this tablo)
      // and test_note_3 (global note shared across all tablos)
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_2");
      expect(noteIds).toContain("test_note_3"); // Global note
      expect(noteIds).toContain("test_note_4");
    });

    it("should return team notes for owner's team tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_team",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);

      // Should include test_note_6 (team note), test_note_4 (in multiple tablos)
      // and test_note_3 (global note)
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_6");
      expect(noteIds).toContain("test_note_4");
      expect(noteIds).toContain("test_note_3"); // Global note
    });
  });

  describe("GET /notes/:tabloId - Global Notes (Shared Across All Tablos)", () => {
    it("should include global notes in any owner's tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_private",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();

      // test_note_3 is a global note (tablo_id = null) and should appear in all tablos
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_3");

      // Find the global note
      const globalNote = data.notes.find((note: Note) => note.id === "test_note_3");
      expect(globalNote).toBeDefined();
      expect(globalNote?.title).toBe("Test Global Note");
    });

    it("should include temp's global notes in temp's tablos", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_temp_shared_admin",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();

      // test_note_9 is temp's global note
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_9");

      // Should also include test_note_8 (shared in this tablo)
      expect(noteIds).toContain("test_note_8");
    });
  });

  describe("GET /notes/:tabloId - Public Notes", () => {
    it("should return public shared notes for owner's shared tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_shared",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();

      // test_note_2 and test_note_4 are public notes in this tablo
      const publicNotes = data.notes.filter(
        (note: Note) => note.id === "test_note_2" || note.id === "test_note_4"
      );
      expect(publicNotes.length).toBeGreaterThanOrEqual(2);
    });

    it("should show public notes to temporary user with tablo access", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_shared",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);
    });
  });

  describe("GET /notes/:tabloId - Note Deduplication", () => {
    it("should not return duplicate notes when shared both globally and specifically", async () => {
      // test_note_4 is shared in both owner_shared and owner_team tablos
      // It should appear only once when querying either tablo
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_shared",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();

      // Count occurrences of test_note_4
      const note4Count = data.notes.filter((note: Note) => note.id === "test_note_4").length;
      expect(note4Count).toBe(1); // Should appear only once despite multiple sharing
    });
  });

  describe("GET /notes/:tabloId - Access Control", () => {
    it("should deny access to notes in tablo user doesn't have access to", async () => {
      // Temp user trying to access owner's private tablo
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_private",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(403);
    });

    it("should deny access to notes in second private tablo", async () => {
      // Temp user trying to access owner's second private tablo
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_private_2",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(403);
    });

    it("should deny unauthenticated access", async () => {
      const res = await getTabloNotesResponse(client, "test_tablo_owner_shared");

      expect(res.status).toBe(401);
    });

    it("should return 404 for non-existent tablo", async () => {
      const res = await getTabloNotesResponse(client, "non_existent_tablo", ownerUser.accessToken);

      // Will return 403 because user doesn't have access
      expect(res.status).toBe(403);
    });
  });

  describe("GET /notes/:tabloId - Temporary User Access", () => {
    it("should return notes for temporary user in shared tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_shared",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);
    });

    it("should return notes for temporary user in team tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_team",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);

      // Should see owner's notes shared in this tablo
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_6"); // Team note
      expect(noteIds).toContain("test_note_3"); // Global note
    });

    it("should return notes for temporary user's own private tablo", async () => {
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_temp_private",
        temporaryUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);

      // Should include temp's global note
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_9"); // Temp's global note
    });
  });

  describe("GET /notes/:tabloId - Edge Cases", () => {
    it("should require tabloId parameter", async () => {
      // This should be caught by routing, but test behavior
      const res = await getTabloNotesResponse(client, "", ownerUser.accessToken);

      // Empty tabloId should result in error or 404
      expect(res.status >= 400).toBe(true);
    });

    it("should handle tablo with no specific notes but include global notes", async () => {
      // test_tablo_owner_done has no specific notes shared, but should include global notes
      const res = await getTabloNotesResponse(
        client,
        "test_tablo_owner_done",
        ownerUser.accessToken
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.notes).toBeDefined();
      expect(Array.isArray(data.notes)).toBe(true);
      // Should at least contain global notes
      const noteIds = data.notes.map((note: Note) => note.id);
      expect(noteIds).toContain("test_note_3"); // Global note should still appear
    });
  });
});
