/**
 * Coverage gap tests for src/server.ts functions flagged by fallow.
 *
 * These fill genuinely untested paths that fallow can't trace through
 * the existing integration-level tests.
 */
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { Database } from "bun:sqlite";
import { WebhookServer } from "../../src/server";
import { getMessageText } from "../../src/http/webhook-handler";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Access private static methods for direct testing. */
const server = WebhookServer as any;

function createTestRequest(body: unknown): Request {
  return new Request("http://localhost:3007", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
}

function createTestRequestRaw(body: string): Request {
  return new Request("http://localhost:3007", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body,
  });
}

// ---------------------------------------------------------------------------
// getMessageText — 5 branches, tested only indirectly
// ---------------------------------------------------------------------------

describe("getMessageText — direct edge cases", () => {
  test("returns empty for null / undefined", () => {
    expect(getMessageText(null)).toBe("");
    expect(getMessageText(undefined)).toBe("");
  });

  test("returns empty for non-object input", () => {
    expect(getMessageText("a string")).toBe("");
    expect(getMessageText(42)).toBe("");
  });

  test("extracts conversation text", () => {
    expect(getMessageText({ conversation: "  hello world  " })).toBe(
      "hello world",
    );
  });

  test("falls back when conversation is empty string", () => {
    expect(
      getMessageText({
        conversation: "",
        extendedTextMessage: { text: "fallback" },
      }),
    ).toBe("fallback");
  });

  test("extracts extendedTextMessage.text", () => {
    expect(
      getMessageText({
        extendedTextMessage: { text: "  extended text  " },
      }),
    ).toBe("extended text");
  });

  test("extracts imageMessage.caption", () => {
    expect(
      getMessageText({
        imageMessage: { caption: "  image caption  " },
      }),
    ).toBe("image caption");
  });

  test("extracts videoMessage.caption", () => {
    expect(
      getMessageText({
        videoMessage: { caption: "  video caption  " },
      }),
    ).toBe("video caption");
  });

  test("returns empty when all fields are empty strings", () => {
    expect(
      getMessageText({
        conversation: "",
        extendedTextMessage: { text: "" },
        imageMessage: { caption: "" },
      }),
    ).toBe("");
  });

  test("returns empty when text is a non-string (edge case)", () => {
    expect(
      getMessageText({ conversation: 12345 }),
    ).toBe("");
  });
});

// ---------------------------------------------------------------------------
// validateEnv — error paths never exercised
// ---------------------------------------------------------------------------

describe("validateEnv — error paths", () => {
  let exitCode: number | null = null;
  let stderr: string[] = [];
  const realExit = process.exit;
  const realConsoleError = console.error;

  beforeEach(() => {
    exitCode = null;
    stderr = [];
    process.exit = ((code?: number) => {
      exitCode = code ?? 1;
      throw new Error(`process.exit(${code})`);
    }) as any;
    console.error = (...args: any[]) => {
      stderr.push(args.map(String).join(" "));
    };
  });

  afterEach(() => {
    process.exit = realExit;
    console.error = realConsoleError;
  });

  test("exits when required env vars are missing", () => {
    // Remove a required var
    const saved = process.env.EVOLUTION_API_URL;
    delete (process.env as any).EVOLUTION_API_URL;

    try {
      server.validateEnv();
    } catch {
      // Expected — process.exit throws
    }
    expect(exitCode).toBe(1);
    expect(stderr.some((l) => l.includes("EVOLUTION_API_URL"))).toBe(true);

    process.env.EVOLUTION_API_URL = saved;
  });

  test("exits when CHATBOT_PHONE_NUMBER contains non-digits", () => {
    // Set all required vars so we reach the phone check
    const saved: Record<string, string | undefined> = {};
    for (const k of ["EVOLUTION_API_URL", "EVOLUTION_API_KEY", "EVOLUTION_API_INSTANCE", "WEBHOOK_URL"]) {
      saved[k] = process.env[k];
      process.env[k] = "set";
    }
    process.env.CHATBOT_PHONE_NUMBER = "55-1234-5678";

    try {
      server.validateEnv();
    } catch {
      // Expected
    }
    expect(exitCode).toBe(1);
    expect(stderr.some((l) => l.includes("digits"))).toBe(true);

    for (const k of Object.keys(saved)) {
      process.env[k] = saved[k];
    }
  });

  test("succeeds when all vars are valid", () => {
    // Ensure required vars are set
    const prev = {
      EVOLUTION_API_URL: process.env.EVOLUTION_API_URL,
      EVOLUTION_API_KEY: process.env.EVOLUTION_API_KEY,
      EVOLUTION_API_INSTANCE: process.env.EVOLUTION_API_INSTANCE,
      CHATBOT_PHONE_NUMBER: process.env.CHATBOT_PHONE_NUMBER,
      WEBHOOK_URL: process.env.WEBHOOK_URL,
    };
    process.env.EVOLUTION_API_URL = "http://localhost:8080";
    process.env.EVOLUTION_API_KEY = "k";
    process.env.EVOLUTION_API_INSTANCE = "i";
    process.env.CHATBOT_PHONE_NUMBER = "1234567890";
    process.env.WEBHOOK_URL = "http://localhost:3000";

    try {
      server.validateEnv();
      // Should not throw
      expect(exitCode).toBeNull();
    } finally {
      Object.assign(process.env, prev);
    }
  });
});

// ---------------------------------------------------------------------------
// contacts.update / chats.update events — never tested
// ---------------------------------------------------------------------------

describe("routeWebhookEvent — contacts.update / chats.update", () => {
  test("handles contacts.update event without crash", async () => {
    const res = await WebhookServer.handleRequest(
      createTestRequest({
        event: "contacts.update",
        instance: "test-instance",
        data: {
          contacts: [
            { id: "1234567890@s.whatsapp.net", name: "Test User" },
          ],
        },
      }),
    );
    expect(res.status).toBe(200);
  });

  test("handles chats.update event without crash", async () => {
    const res = await WebhookServer.handleRequest(
      createTestRequest({
        event: "chats.update",
        instance: "test-instance",
        data: {
          chats: [
            { id: "group-id@g.us", name: "Updated Chat" },
          ],
        },
      }),
    );
    expect(res.status).toBe(200);
  });

  test("handles contacts.update with empty data gracefully", async () => {
    const res = await WebhookServer.handleRequest(
      createTestRequest({
        event: "contacts.update",
        instance: "test-instance",
        data: {},
      }),
    );
    expect(res.status).toBe(200);
  });
});

// ---------------------------------------------------------------------------
// Error path: malformed JSON body
// ---------------------------------------------------------------------------

describe("handleRequest — error paths", () => {
  test("returns 400 for malformed JSON body", async () => {
    const req = createTestRequestRaw("{not valid json");
    const res = await WebhookServer.handleRequest(req);
    expect(res.status).toBe(400);
  });

  test("returns 400 for empty body", async () => {
    const req = new Request("http://localhost:3007", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
    });
    const res = await WebhookServer.handleRequest(req);
    expect(res.status).toBe(400);
  });
});

// ---------------------------------------------------------------------------
// getBaseUrl — never tested
// ---------------------------------------------------------------------------

describe("getBaseUrl", () => {
  test("uses x-forwarded-proto and x-forwarded-host when present", () => {
    const req = new Request("http://localhost:3007", {
      headers: {
        "x-forwarded-proto": "https",
        "x-forwarded-host": "example.com",
      },
    });
    expect(server.getBaseUrl(req)).toBe("https://example.com");
  });

  test("falls back to host header when no forwarded headers", () => {
    const req = new Request("http://myhost.local:3007", {
      headers: { host: "myhost.local:3007" },
    });
    expect(server.getBaseUrl(req)).toBe("http://myhost.local:3007");
  });

  test("uses http when no proto and no forwarded proto", () => {
    const req = new Request("http://10.0.0.1:3007", {
      headers: { host: "10.0.0.1:3007" },
    });
    expect(server.getBaseUrl(req)).toBe("http://10.0.0.1:3007");
  });
});
