/**
 * Unit tests for Voice Agent Server Tools:
 * - /api/voice-agent/server-tools/booking (POST)
 * - /api/voice-agent/server-tools/availability (POST)
 */

// Mock dependencies BEFORE imports
jest.mock("@/lib/logger", () => ({
  logger: {
    warn: jest.fn(),
    error: jest.fn(),
    info: jest.fn(),
    debug: jest.fn(),
    scope: jest.fn(() => ({
      warn: jest.fn(),
      error: jest.fn(),
      info: jest.fn(),
      debug: jest.fn(),
    })),
  },
}));

jest.mock("@/lib/db");

jest.mock("@/lib/generated/prisma", () => ({
  Prisma: { JsonNull: "DbNull" },
}));

jest.mock("@paralleldrive/cuid2", () => ({
  createId: jest.fn(() => "cuid-test-123"),
}));

jest.mock("@/lib/services/appointment-type.service", () => ({
  AppointmentTypeService: {
    findTypeByName: jest.fn(),
  },
}));

jest.mock("@/lib/utils/organization-data", () => ({
  getEffectiveDataOwnerId: jest.fn(),
}));

jest.mock("next/server", () => ({
  NextRequest: jest.fn(),
  NextResponse: {
    json: jest.fn((data, options) => ({
      status: options?.status || 200,
      headers: new Map(),
      json: async () => data,
    })),
  },
}));

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { getEffectiveDataOwnerId } from "@/lib/utils/organization-data";
import { AppointmentTypeService } from "@/lib/services/appointment-type.service";

// ─── Booking Server Tool ─────────────────────────────────────────────────

// Voice agent routes not yet implemented
describe.skip("POST /api/voice-agent/server-tools/booking", () => {
  let bookingModule: any;

  beforeAll(async () => {
    bookingModule =
      await import("@/app/api/voice-agent/server-tools/booking/route");
  });

  beforeEach(() => {
    jest.clearAllMocks();
    (NextResponse.json as jest.Mock).mockImplementation((data, options) => ({
      status: options?.status || 200,
      headers: new Map(),
      json: async () => data,
    }));
  });

  const mockRequest = (body: any, token?: string) =>
    ({
      json: jest.fn().mockResolvedValue(body),
      nextUrl: {
        searchParams: new URLSearchParams(token ? { token } : {}),
      },
    }) as unknown as NextRequest;

  it("should return 401 without token", async () => {
    const response = await bookingModule.POST(mockRequest({}));
    const body = await response.json();
    expect(response.status).toBe(401);
    expect(body.error).toBe("Unauthorized");
  });

  it("should return 401 with invalid token", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue(null);

    const response = await bookingModule.POST(mockRequest({}, "bad-token"));
    const body = await response.json();
    expect(response.status).toBe(401);
  });

  it("should create booking successfully", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockResolvedValue({
      organizationId: "org-1",
    });
    (AppointmentTypeService.findTypeByName as jest.Mock).mockResolvedValue({
      duration: 60,
      price: 100,
    });
    (prisma.bookings.create as jest.Mock).mockResolvedValue({});
    (prisma.workflowAnalytics.upsert as jest.Mock).mockResolvedValue({});

    const response = await bookingModule.POST(
      mockRequest(
        {
          action: "create",
          customer_name: "John",
          customer_phone: "+97412345678",
          service: "Haircut",
          date: "2026-03-01",
          time: "10:00",
        },
        "valid-token",
      ),
    );
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.booking_id).toBe("cuid-test-123");
    expect(prisma.bookings.create).toHaveBeenCalled();
  });

  it("should return error for missing fields on create", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockResolvedValue({
      organizationId: null,
    });

    const response = await bookingModule.POST(
      mockRequest({ action: "create", customer_name: "John" }, "valid-token"),
    );
    const body = await response.json();
    expect(body.success).toBe(false);
    expect(body.message).toContain("Missing required fields");
  });

  it("should cancel booking successfully", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockResolvedValue({
      organizationId: null,
    });
    (prisma.bookings.findFirst as jest.Mock).mockResolvedValue({
      id: "booking-1",
      service: "Haircut",
      appointmentDate: new Date("2026-03-01"),
      appointmentTime: "10:00",
    });
    (prisma.bookings.update as jest.Mock).mockResolvedValue({});

    const response = await bookingModule.POST(
      mockRequest({ action: "cancel", booking_id: "booking-1" }, "valid-token"),
    );
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.message).toContain("cancelled");
  });

  it("should return error for cancel with no matching booking", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockResolvedValue({
      organizationId: null,
    });
    (prisma.bookings.findFirst as jest.Mock).mockResolvedValue(null);

    const response = await bookingModule.POST(
      mockRequest(
        { action: "cancel", booking_id: "nonexistent" },
        "valid-token",
      ),
    );
    const body = await response.json();
    expect(body.success).toBe(false);
    expect(body.message).toContain("No matching appointment");
  });

  it("should reschedule booking successfully", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockResolvedValue({
      organizationId: null,
    });
    (prisma.bookings.findFirst as jest.Mock).mockResolvedValue({
      id: "booking-1",
    });
    (prisma.bookings.update as jest.Mock).mockResolvedValue({});

    const response = await bookingModule.POST(
      mockRequest(
        {
          action: "reschedule",
          booking_id: "booking-1",
          new_date: "2026-03-05",
          new_time: "14:00",
        },
        "valid-token",
      ),
    );
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.message).toContain("rescheduled");
  });

  it("should return error for unknown action", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockResolvedValue({
      organizationId: null,
    });

    const response = await bookingModule.POST(
      mockRequest({ action: "unknown_action" }, "valid-token"),
    );
    const body = await response.json();
    expect(body.success).toBe(false);
    expect(body.message).toContain("Unknown action");
  });

  it("should return 500 on unexpected error", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (getEffectiveDataOwnerId as jest.Mock).mockRejectedValue(
      new Error("DB error"),
    );

    const response = await bookingModule.POST(
      mockRequest({ action: "create" }, "valid-token"),
    );
    const body = await response.json();
    expect(response.status).toBe(500);
    expect(body.success).toBe(false);
  });
});

// ─── Availability Server Tool ─────────────────────────────────────────────

// Voice agent routes not yet implemented
describe.skip("POST /api/voice-agent/server-tools/availability", () => {
  let availModule: any;

  beforeAll(async () => {
    availModule =
      await import("@/app/api/voice-agent/server-tools/availability/route");
  });

  beforeEach(() => {
    jest.clearAllMocks();
    (NextResponse.json as jest.Mock).mockImplementation((data, options) => ({
      status: options?.status || 200,
      headers: new Map(),
      json: async () => data,
    }));
  });

  const mockRequest = (body: any, token?: string) =>
    ({
      json: jest.fn().mockResolvedValue(body),
      nextUrl: {
        searchParams: new URLSearchParams(token ? { token } : {}),
      },
    }) as unknown as NextRequest;

  it("should return 401 without token", async () => {
    const response = await availModule.POST(mockRequest({}));
    const body = await response.json();
    expect(response.status).toBe(401);
  });

  it("should return error when no date provided", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });

    const response = await availModule.POST(mockRequest({}, "valid-token"));
    const body = await response.json();
    expect(body.success).toBe(false);
    expect(body.message).toContain("date");
  });

  it("should return available slots", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (prisma.bookings.findMany as jest.Mock).mockResolvedValue([
      { appointmentTime: "10:00", duration: 30, service: "Haircut" },
    ]);
    (prisma.users.findUnique as jest.Mock).mockResolvedValue({
      voiceAgentConfig: null,
    });

    const response = await availModule.POST(
      mockRequest({ date: "2026-03-03" }, "valid-token"),
    );
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.available_slots).toBeDefined();
    expect(body.available_slots.length).toBeGreaterThan(0);
    // 10:00 should be excluded (booked)
    expect(body.available_slots).not.toContain("10:00");
  });

  it("should return closed message for closed day", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (prisma.bookings.findMany as jest.Mock).mockResolvedValue([]);
    (prisma.users.findUnique as jest.Mock).mockResolvedValue({
      voiceAgentConfig: {
        businessHours: {
          schedule: [{ day: "monday", isOpen: false }],
        },
      },
    });

    // Use a known Monday date
    const response = await availModule.POST(
      mockRequest({ date: "2026-03-02" }, "valid-token"), // March 2, 2026 is Monday
    );
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.available_slots).toEqual([]);
    expect(body.message).toContain("closed");
  });

  it("should return 500 on error", async () => {
    (prisma.users.findFirst as jest.Mock).mockResolvedValue({ id: "user-1" });
    (prisma.bookings.findMany as jest.Mock).mockRejectedValue(
      new Error("DB error"),
    );

    const response = await availModule.POST(
      mockRequest({ date: "2026-03-03" }, "valid-token"),
    );
    const body = await response.json();
    expect(response.status).toBe(500);
  });
});
