import { createClient } from "@supabase/supabase-js";
import { testClient } from "hono/testing";
import { beforeAll, 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 TimeSlot {
  date: string;
  time: string;
  available: boolean;
}

describe("Public 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 supabase = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY);
  const ownerUser = getTestUser("owner");
  let ownerShortId: string;
  let eventTypeStandardName: string;

  // Helper function to get public slots
  const getPublicSlots = async (
    // biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
    client: any,
    shortUserId: string,
    standardName: string
  ) => {
    return await client.public.slots[":shortUserId"][":standardName"].$get({
      param: {
        shortUserId,
        standardName,
      },
    });
  };

  beforeAll(async () => {
    // Get owner's short_user_id
    const { data: ownerProfile } = await supabase
      .from("profiles")
      .select("short_user_id")
      .eq("id", ownerUser.userId)
      .single();

    ownerShortId = ownerProfile?.short_user_id || "";

    // Use existing test event type that's already in the database
    eventTypeStandardName = "test-consultation";
  });

  describe("GET /public/slots/:shortUserId/:standardName - Valid Requests", () => {
    it("should successfully get public slots for valid user and event type", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      // Verify response structure
      expect(data.user).toBeDefined();
      expect(data.user.name).toBeDefined();
      expect(data.eventType).toBeDefined();
      expect(data.slots).toBeDefined();
      expect(data.availableSlots).toBeDefined();
      expect(Array.isArray(data.availableSlots)).toBe(true);
    });

    it("should return user information in response", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      expect(data.user).toBeDefined();
      expect(data.user.name).toBeDefined();
      expect(typeof data.user.name).toBe("string");
    });

    it("should return event type configuration in response", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      expect(data.eventType).toBeDefined();
      expect(data.eventType.name).toBeDefined();
      expect(data.eventType.duration).toBeDefined();
      expect(typeof data.eventType.duration).toBe("number");
    });

    it("should return slots grouped by date", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      expect(data.slots).toBeDefined();
      expect(typeof data.slots).toBe("object");

      // Check if slots are grouped by date (YYYY-MM-DD format)
      const dateKeys = Object.keys(data.slots);
      if (dateKeys.length > 0) {
        expect(dateKeys[0]).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      }
    });

    it("should return available slots array", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      expect(Array.isArray(data.availableSlots)).toBe(true);

      // Check structure of available slots if any exist
      if (data.availableSlots.length > 0) {
        const slot = data.availableSlots[0];
        expect(slot.date).toBeDefined();
        expect(slot.time).toBeDefined();
        expect(slot.available).toBe(true);
      }
    });

    it("should generate slots for approximately the next two months", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      const dateKeys = Object.keys(data.slots);
      // Should have slots for multiple days (at least some days in the next 2 months)
      // We don't check exact number as it depends on availability configuration
      expect(dateKeys.length).toBeGreaterThan(0);
    });
  });

  describe("GET /public/slots/:shortUserId/:standardName - Invalid User", () => {
    it("should return 404 for non-existent user short ID", async () => {
      const res = await getPublicSlots(client, "nonexistent123", eventTypeStandardName);

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("User not found");
    });

    it("should return error for empty short user ID", async () => {
      const res = await getPublicSlots(client, "", eventTypeStandardName);

      // Empty parameter doesn't match route, falls through to auth middleware
      expect(res.status).toBe(401);
    });

    it("should return 404 for invalid short user ID format", async () => {
      const res = await getPublicSlots(client, "invalid_user_12345", eventTypeStandardName);

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("User not found");
    });
  });

  describe("GET /public/slots/:shortUserId/:standardName - Invalid Event Type", () => {
    it("should return 404 for non-existent event type standard name", async () => {
      const res = await getPublicSlots(client, ownerShortId, "non-existent-event-type");

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("Event type not found");
    });

    it("should return error for empty standard name", async () => {
      const res = await getPublicSlots(client, ownerShortId, "");

      // Empty parameter doesn't match route, falls through to auth middleware
      expect(res.status).toBe(401);
    });

    it("should return 404 for event type belonging to different user", async () => {
      // Try to access owner's event type with a non-existent user
      const res = await getPublicSlots(client, "differentuser123", eventTypeStandardName);

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("User not found");
    });
  });

  describe("GET /public/slots/:shortUserId/:standardName - Edge Cases", () => {
    it("should handle special characters in short user ID", async () => {
      const res = await getPublicSlots(client, "user-with-special", eventTypeStandardName);

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("User not found");
    });

    it("should handle special characters in standard name", async () => {
      const res = await getPublicSlots(client, ownerShortId, "event-with-special");

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("Event type not found");
    });

    it("should handle very long short user ID", async () => {
      const longUserId = "a".repeat(50);
      const res = await getPublicSlots(client, longUserId, eventTypeStandardName);

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("User not found");
    });

    it("should handle very long standard name", async () => {
      const longStandardName = "event-name-".repeat(20);
      const res = await getPublicSlots(client, ownerShortId, longStandardName);

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("Event type not found");
    });
  });

  describe("GET /public/slots/:shortUserId/:standardName - No Authentication Required", () => {
    it("should work without any authentication headers", async () => {
      // This endpoint should be public and not require authentication
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.user).toBeDefined();
      expect(data.eventType).toBeDefined();
      expect(data.slots).toBeDefined();
    });

    it("should work with invalid authentication headers (should be ignored)", async () => {
      // Even with invalid auth, public endpoint should still work
      const res = await client.public.slots[":shortUserId"][":standardName"].$get(
        {
          param: {
            shortUserId: ownerShortId,
            standardName: eventTypeStandardName,
          },
        },
        {
          headers: {
            Authorization: "Bearer invalid_token_12345",
          },
        }
      );

      // Should still work as this is a public endpoint
      expect(res.status).toBe(200);
    });
  });

  describe("GET /public/slots/:shortUserId/:standardName - Response Validation", () => {
    it("should not include sensitive user information", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      // Should only include public user info (name, avatar_url)
      expect(data.user).toBeDefined();
      expect(data.user.name).toBeDefined();

      // Should NOT include sensitive info
      expect(data.user.email).toBeUndefined();
      expect(data.user.id).toBeUndefined();
      expect(data.user.short_user_id).toBeUndefined();
    });

    it("should include only necessary event type configuration", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      expect(data.eventType).toBeDefined();
      expect(data.eventType.name).toBeDefined();
      expect(data.eventType.duration).toBeDefined();

      // Should not include internal fields like user_id, deleted_at
      expect(data.eventType.user_id).toBeUndefined();
      expect(data.eventType.deleted_at).toBeUndefined();
    });

    it("should have slots with correct structure", async () => {
      const res = await getPublicSlots(client, ownerShortId, eventTypeStandardName);

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

      // Get all slots from slotsByDate
      const allSlots: TimeSlot[] = [];
      Object.values(data.slots).forEach((daySlots) => {
        allSlots.push(...(daySlots as TimeSlot[]));
      });

      if (allSlots.length > 0) {
        const slot = allSlots[0];
        expect(slot).toHaveProperty("date");
        expect(slot).toHaveProperty("time");
        expect(slot).toHaveProperty("available");
      }
    });
  });
});
