/**
 * Unit tests for Twilio Voice routes:
 * - /api/voice/twilio/numbers (GET, POST)
 * - /api/voice/twilio/status (POST)
 */

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

jest.mock("@/lib/auth-helpers", () => ({
  getAuthenticatedUser: jest.fn(),
}));

jest.mock("@/lib/services/twilio-voice.service", () => ({
  twilioVoiceService: {
    configureWebhooks: jest.fn(),
  },
  twilioVoiceDb: {
    getUserPhoneNumbers: jest.fn(),
    importPhoneNumber: jest.fn(),
    getVoiceCallBySid: jest.fn(),
    updateVoiceCallStatus: jest.fn(),
    updateVoiceCallTranscript: jest.fn(),
  },
  TwilioVoiceServiceError: class TwilioVoiceServiceError extends Error {
    code: string;
    statusCode: number;
    constructor(message: string, code: string, statusCode: number) {
      super(message);
      this.code = code;
      this.statusCode = statusCode;
    }
  },
}));

jest.mock("@/lib/services/elevenlabs.service", () => ({
  elevenLabsService: {
    linkPhoneNumberToAgent: jest.fn(),
    getConversationDetails: jest.fn(),
  },
}));

jest.mock("@/lib/services/lead-capture.service", () => ({
  captureFromVoiceAgent: jest.fn(),
}));

jest.mock("@/lib/csrf", () => ({
  validateCSRFToken: jest.fn(),
}));

jest.mock("@/lib/validators/twilio-voice.validator", () => ({
  importPhoneNumberSchema: {
    safeParse: jest.fn(),
  },
  twilioCallStatusWebhookSchema: {
    safeParse: jest.fn(),
  },
  twilioCallStatusSchema: {
    safeParse: jest.fn(),
  },
}));

jest.mock("@/lib/types/twilio-voice.types", () => ({
  mapTwilioStatusToCallStatus: jest.fn(),
}));

jest.mock("next/server", () => {
  const NextResponseMock = function (body: any, init?: any) {
    return {
      status: init?.status || 200,
      headers: new Map(Object.entries(init?.headers || {})),
      json: async () => (body ? JSON.parse(body) : null),
      body,
    };
  };
  NextResponseMock.json = jest.fn((data: any, options?: any) => ({
    status: options?.status || 200,
    headers: new Map(Object.entries(options?.headers || {})),
    json: async () => data,
  }));
  return {
    NextRequest: jest.fn(),
    NextResponse: NextResponseMock,
  };
});

import { NextRequest, NextResponse } from "next/server";
import { getAuthenticatedUser } from "@/lib/auth-helpers";
import {
  twilioVoiceDb,
  TwilioVoiceServiceError,
} from "@/lib/services/twilio-voice.service";
import { validateCSRFToken } from "@/lib/csrf";
import { importPhoneNumberSchema } from "@/lib/validators/twilio-voice.validator";
import {
  twilioCallStatusWebhookSchema,
  twilioCallStatusSchema,
} from "@/lib/validators/twilio-voice.validator";
import { mapTwilioStatusToCallStatus } from "@/lib/types/twilio-voice.types";

// Voice agent routes not yet implemented
describe.skip("GET /api/voice/twilio/numbers", () => {
  let numbersModule: any;

  beforeAll(async () => {
    numbersModule = await import("@/app/api/voice/twilio/numbers/route");
  });

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

  const mockRequest = () =>
    ({
      headers: { get: jest.fn(() => null) },
    }) as unknown as NextRequest;

  it("should return 401 if not authenticated", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue(null);

    const response = await numbersModule.GET(mockRequest());
    const body = await response.json();
    expect(response.status).toBe(401);
    expect(body.error).toBe("Unauthorized");
  });

  it("should return phone numbers list", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue({
      user: { id: "user-1" },
    });
    (twilioVoiceDb.getUserPhoneNumbers as jest.Mock).mockResolvedValue([
      {
        id: "pn-1",
        phoneNumber: "+97412345678",
        friendlyName: "Main Line",
        status: "active",
        supportsInbound: true,
        supportsOutbound: true,
        elevenLabsAgentId: "agent-1",
        lastUsedAt: null,
        createdAt: new Date(),
        twilioAccountSid: "secret-should-not-expose",
      },
    ]);

    const response = await numbersModule.GET(mockRequest());
    const body = await response.json();
    expect(body.total).toBe(1);
    expect(body.phoneNumbers[0].phoneNumber).toBe("+97412345678");
    // Should not expose credentials
    expect(body.phoneNumbers[0].twilioAccountSid).toBeUndefined();
  });

  it("should return 500 on error", async () => {
    (getAuthenticatedUser as jest.Mock).mockRejectedValue(
      new Error("DB error"),
    );

    const response = await numbersModule.GET(mockRequest());
    const body = await response.json();
    expect(response.status).toBe(500);
  });
});

// Voice agent routes not yet implemented
describe.skip("POST /api/voice/twilio/numbers", () => {
  let numbersModule: any;

  beforeAll(async () => {
    numbersModule = await import("@/app/api/voice/twilio/numbers/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) =>
    ({
      headers: { get: jest.fn(() => null) },
      cookies: { get: jest.fn(() => undefined) },
      json: jest.fn().mockResolvedValue(body || {}),
    }) as unknown as NextRequest;

  it("should return 401 if not authenticated", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue(null);

    const response = await numbersModule.POST(mockRequest());
    const body = await response.json();
    expect(response.status).toBe(401);
  });

  it("should return 403 if CSRF token invalid", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue({
      user: { id: "user-1" },
    });
    (validateCSRFToken as jest.Mock).mockReturnValue(false);

    const response = await numbersModule.POST(mockRequest());
    const body = await response.json();
    expect(response.status).toBe(403);
    expect(body.error).toBe("Invalid CSRF token");
  });

  it("should return 400 for invalid body", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue({
      user: { id: "user-1" },
    });
    (validateCSRFToken as jest.Mock).mockReturnValue(true);
    (importPhoneNumberSchema.safeParse as jest.Mock).mockReturnValue({
      success: false,
      error: { errors: [{ message: "Required" }] },
    });

    const response = await numbersModule.POST(mockRequest({}));
    const body = await response.json();
    expect(response.status).toBe(400);
  });

  it("should import phone number successfully", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue({
      user: { id: "user-1" },
    });
    (validateCSRFToken as jest.Mock).mockReturnValue(true);
    (importPhoneNumberSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
      data: {
        phoneNumber: "+97412345678",
        twilioAccountSid: "AC123",
        twilioAuthToken: "token123",
        friendlyName: "Main Line",
      },
    });
    (twilioVoiceDb.importPhoneNumber as jest.Mock).mockResolvedValue({
      id: "pn-1",
      phoneNumber: "+97412345678",
      friendlyName: "Main Line",
      status: "active",
      supportsInbound: true,
      supportsOutbound: true,
      elevenLabsAgentId: null,
      createdAt: new Date(),
    });

    const response = await numbersModule.POST(mockRequest({}));
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.phoneNumber.phoneNumber).toBe("+97412345678");
  });

  it("should return 500 if import fails", async () => {
    (getAuthenticatedUser as jest.Mock).mockResolvedValue({
      user: { id: "user-1" },
    });
    (validateCSRFToken as jest.Mock).mockReturnValue(true);
    (importPhoneNumberSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
      data: {
        phoneNumber: "+97412345678",
        twilioAccountSid: "AC123",
        twilioAuthToken: "token123",
      },
    });
    (twilioVoiceDb.importPhoneNumber as jest.Mock).mockResolvedValue(null);

    const response = await numbersModule.POST(mockRequest({}));
    const body = await response.json();
    expect(response.status).toBe(500);
  });
});

// Voice agent routes not yet implemented
describe.skip("POST /api/voice/twilio/status", () => {
  let statusModule: any;

  beforeAll(async () => {
    statusModule = await import("@/app/api/voice/twilio/status/route");
  });

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

  const mockFormRequest = (params: Record<string, string>) => {
    const formData = new Map(Object.entries(params));
    return {
      formData: jest.fn().mockResolvedValue({
        forEach: (fn: (value: string, key: string) => void) => {
          formData.forEach((value, key) => fn(value, key));
        },
      }),
    } as unknown as NextRequest;
  };

  it("should return received:true for unknown status", async () => {
    (twilioCallStatusWebhookSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
    });
    (twilioCallStatusSchema.safeParse as jest.Mock).mockReturnValue({
      success: false,
    });

    const response = await statusModule.POST(
      mockFormRequest({ CallSid: "CA123", CallStatus: "unknown-status" }),
    );
    const body = await response.json();
    expect(body.received).toBe(true);
  });

  it("should return received:true for unknown call SID", async () => {
    (twilioCallStatusWebhookSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
    });
    (twilioCallStatusSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
      data: "completed",
    });
    (mapTwilioStatusToCallStatus as jest.Mock).mockReturnValue("COMPLETED");
    (twilioVoiceDb.getVoiceCallBySid as jest.Mock).mockResolvedValue(null);

    const response = await statusModule.POST(
      mockFormRequest({ CallSid: "CA999", CallStatus: "completed" }),
    );
    const body = await response.json();
    expect(body.received).toBe(true);
  });

  it("should update call status for completed call", async () => {
    (twilioCallStatusWebhookSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
    });
    (twilioCallStatusSchema.safeParse as jest.Mock).mockReturnValue({
      success: true,
      data: "completed",
    });
    (mapTwilioStatusToCallStatus as jest.Mock).mockReturnValue("COMPLETED");
    (twilioVoiceDb.getVoiceCallBySid as jest.Mock).mockResolvedValue({
      id: "call-1",
      elevenLabsConversationId: null,
    });
    (twilioVoiceDb.updateVoiceCallStatus as jest.Mock).mockResolvedValue({});

    const response = await statusModule.POST(
      mockFormRequest({
        CallSid: "CA123",
        CallStatus: "completed",
        Duration: "120",
      }),
    );
    const body = await response.json();
    expect(body.received).toBe(true);
    expect(body.status).toBe("COMPLETED");
    expect(twilioVoiceDb.updateVoiceCallStatus).toHaveBeenCalled();
  });

  it("should handle error gracefully", async () => {
    const mockReq = {
      formData: jest.fn().mockRejectedValue(new Error("Parse error")),
    } as unknown as NextRequest;

    const response = await statusModule.POST(mockReq);
    const body = await response.json();
    expect(body.received).toBe(true);
    expect(body.error).toBeDefined();
  });
});
