import { afterEach, describe, expect, it, vi } from "vitest";
import type { BrowserFallbackPort } from "../src/providers/types";

describe("provider runtime coverage seams", () => {
  afterEach(() => {
    vi.unstubAllGlobals();
    vi.doUnmock("../src/providers/browser-fallback");
    vi.doUnmock("../src/providers/errors");
    vi.resetModules();
  });

  it("rethrows the original fetch error when browser fallback declines runtime recovery", async () => {
    vi.resetModules();
    const actual = await vi.importActual<typeof import("../src/providers/browser-fallback")>(
      "../src/providers/browser-fallback"
    );
    const resolveProviderBrowserFallback = vi.fn(async () => null);
    vi.doMock("../src/providers/browser-fallback", () => ({
      ...actual,
      resolveProviderBrowserFallback
    }));

    vi.stubGlobal("fetch", vi.fn(async () => {
      throw new Error("ECONNRESET runtime coverage seam");
    }) as unknown as typeof fetch);

    const { createDefaultRuntime } = await import("../src/providers");
    const browserFallbackPort: BrowserFallbackPort = {
      resolve: vi.fn(async () => ({
        ok: true,
        reasonCode: "env_limited",
        output: {}
      }))
    };

    const runtime = createDefaultRuntime({}, { browserFallbackPort });
    const result = await runtime.search(
      { query: "browser automation", limit: 3 },
      { source: "social", providerIds: ["social/facebook"] }
    );

    expect(result.ok).toBe(false);
    expect(result.records).toEqual([]);
    expect(result.failures).toHaveLength(1);
    expect(result.failures[0]?.error).toMatchObject({
      code: "network",
      message: "Failed to retrieve https://www.facebook.com/watch/search/?q=browser%20automation&page=1",
      provider: "social/facebook",
      source: "social"
    });
    expect(resolveProviderBrowserFallback).toHaveBeenCalledWith(expect.objectContaining({
      provider: "social/facebook",
      source: "social",
      operation: "search",
      reasonCode: "env_limited"
    }));
  });

  it("exposes linkedin and facebook extension-first recovery hints through the registered provider adapter", async () => {
    vi.resetModules();
    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime();
    const facebook = runtime.listProviders().find((provider) => provider.id === "social/facebook");
    const linkedin = runtime.listProviders().find((provider) => provider.id === "social/linkedin");
    const instagram = runtime.listProviders().find((provider) => provider.id === "social/instagram");

    expect(facebook?.recoveryHints?.()).toEqual({
      preferredFallbackModes: ["extension", "managed_headed"]
    });
    expect(linkedin?.recoveryHints?.()).toEqual({
      preferredFallbackModes: ["extension", "managed_headed"]
    });
    expect(instagram?.recoveryHints).toBeUndefined();
  });

  it("maps upstream normalized runtime errors without explicit reason codes to ip_blocked fallback", async () => {
    vi.resetModules();
    const actualErrors = await vi.importActual<typeof import("../src/providers/errors")>(
      "../src/providers/errors"
    );
    vi.doMock("../src/providers/errors", () => ({
      ...actualErrors,
      toProviderError: vi.fn(() => ({
        code: "upstream",
        message: "synthetic upstream",
        retryable: false
      }))
    }));

    vi.stubGlobal("fetch", vi.fn(async () => {
      throw new Error("ignored upstream transport");
    }) as unknown as typeof fetch);

    const resolve = vi.fn(async (request: { reasonCode: string; url?: string }) => ({
      ok: true,
      reasonCode: request.reasonCode as "ip_blocked",
      output: {
        url: request.url ?? "https://www.facebook.com/watch/search/?q=browser%20automation&page=1",
        html: "<html><body><main>fallback upstream content <a href=\"https://www.facebook.com/watch/?v=123456789012345\">video</a></main></body></html>"
      }
    }));

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime({}, {
      browserFallbackPort: {
        resolve
      }
    });
    const result = await runtime.search(
      { query: "browser automation", limit: 3 },
      { source: "social", providerIds: ["social/facebook"] }
    );

    expect(result.ok).toBe(true);
    expect(resolve).toHaveBeenCalledWith(expect.objectContaining({
      provider: "social/facebook",
      reasonCode: "ip_blocked"
    }));
  });

  it("falls back to env_limited when normalized runtime errors have no mapped fallback reason", async () => {
    vi.resetModules();
    const actualErrors = await vi.importActual<typeof import("../src/providers/errors")>(
      "../src/providers/errors"
    );
    vi.doMock("../src/providers/errors", () => ({
      ...actualErrors,
      toProviderError: vi.fn(() => ({
        code: "auth",
        message: "synthetic auth without reason",
        retryable: false
      }))
    }));

    vi.stubGlobal("fetch", vi.fn(async () => {
      throw new Error("ignored auth transport");
    }) as unknown as typeof fetch);

    const resolve = vi.fn(async (request: { reasonCode: string; url?: string }) => ({
      ok: true,
      reasonCode: request.reasonCode as "env_limited",
      output: {
        url: request.url ?? "https://www.facebook.com/watch/search/?q=browser%20automation&page=1",
        html: "<html><body><main>fallback auth content <a href=\"https://www.facebook.com/watch/?v=123456789012345\">video</a></main></body></html>"
      }
    }));

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime({}, {
      browserFallbackPort: {
        resolve
      }
    });
    const result = await runtime.search(
      { query: "browser automation", limit: 3 },
      { source: "social", providerIds: ["social/facebook"] }
    );

    expect(result.ok).toBe(true);
    expect(resolve).toHaveBeenCalledWith(expect.objectContaining({
      provider: "social/facebook",
      reasonCode: "env_limited"
    }));
  });

  it("uses provider map ids when sequential failure envelopes have no attempted order entries", async () => {
    vi.resetModules();
    const { ProviderRuntime } = await import("../src/providers");
    const runtime = new ProviderRuntime();
    const ghostProvider = {
      id: "web/ghost",
      source: "web",
      search: async () => [],
      capabilities: () => ({
        providerId: "web/ghost",
        source: "web" as const,
        operations: {
          search: { op: "search" as const, supported: true },
          fetch: { op: "fetch" as const, supported: false },
          crawl: { op: "crawl" as const, supported: false },
          post: { op: "post" as const, supported: false }
        },
        policy: {
          posting: "unsupported" as const,
          riskNoticeRequired: false,
          confirmationRequired: false
        },
        metadata: {}
      })
    };

    const providers = {
      [Symbol.iterator]: function* () {
        return;
      },
      map: (mapper: (provider: typeof ghostProvider) => string) => [mapper(ghostProvider)]
    } as unknown as typeof ghostProvider[];

    const internals = runtime as unknown as {
      executeSequential: (
        providers: typeof ghostProvider[],
        operation: "search",
        input: { query: string },
        trace: { requestId: string; ts: string },
        timeoutMs: number,
        selection: "auto",
        startedAt: number,
        tierMetadata: { selected: "A"; reasonCode: string }
      ) => Promise<{ ok: boolean; providerOrder: string[]; failures: unknown[] }>;
    };

    const result = await internals.executeSequential(
      providers,
      "search",
      { query: "ghost" },
      { requestId: "providers-runtime-coverage", ts: "2026-03-23T00:00:00.000Z" },
      50,
      "auto",
      Date.now(),
      { selected: "A", reasonCode: "default_tier" }
    );

    expect(result.ok).toBe(false);
    expect(result.failures).toEqual([]);
    expect(result.providerOrder).toEqual(["web/ghost"]);
  });

  it("passes browser transport run options through provider context", async () => {
    vi.resetModules();
    const { ProviderRuntime } = await import("../src/providers");
    const runtime = new ProviderRuntime();
    let capturedContext: Record<string, unknown> | undefined;

    runtime.register({
      id: "web/context-hints",
      source: "web",
      search: vi.fn(async (_input, context) => {
        capturedContext = context as unknown as Record<string, unknown>;
        return [];
      }),
      capabilities: () => ({
        providerId: "web/context-hints",
        source: "web" as const,
        operations: {
          search: { op: "search" as const, supported: true },
          fetch: { op: "fetch" as const, supported: false },
          crawl: { op: "crawl" as const, supported: false },
          post: { op: "post" as const, supported: false }
        },
        policy: {
          posting: "unsupported" as const,
          riskNoticeRequired: false,
          confirmationRequired: false
        },
        metadata: {}
      })
    });

    const result = await runtime.search(
      { query: "context" },
      {
        source: "web",
        providerIds: ["web/context-hints"],
        runtimePolicy: {
          browserMode: "extension",
          challengeAutomationMode: "browser_with_helper"
        }
      }
    );

    expect(result.ok).toBe(true);
    expect(capturedContext).toMatchObject({
      runtimePolicy: {
        browser: {
          preferredModes: ["extension"],
          forceTransport: true
        },
        challenge: expect.objectContaining({
          mode: "browser_with_helper"
        })
      }
    });
  });

  it("fails forced browser transport when no browser port is available", async () => {
    vi.resetModules();
    const fetchMock = vi.fn(async () => ({
      status: 200,
      url: "https://duckduckgo.com/html/?q=forced&ia=web",
      text: async () => "<html><body>direct fetch should not run</body></html>"
    })) as unknown as typeof fetch;
    vi.stubGlobal("fetch", fetchMock);

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime();
    const result = await runtime.search(
      { query: "forced", limit: 1 },
      {
        source: "web",
        providerIds: ["web/default"],
        runtimePolicy: {
          browserMode: "extension",
          useCookies: true,
          cookiePolicyOverride: "required"
        }
      }
    );

    expect(fetchMock).not.toHaveBeenCalled();
    expect(result.ok).toBe(false);
    expect(result.failures[0]?.error).toMatchObject({
      code: "auth",
      reasonCode: "auth_required",
      details: {
        browserTransportRequired: true
      }
    });
  });

  it("keeps forced extension transport unavailable when cookies are not required", async () => {
    vi.resetModules();
    const fetchMock = vi.fn(async () => ({
      status: 200,
      url: "https://duckduckgo.com/html/?q=forced&ia=web",
      text: async () => "<html><body>direct fetch should not run</body></html>"
    })) as unknown as typeof fetch;
    vi.stubGlobal("fetch", fetchMock);

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime();
    const result = await runtime.search(
      { query: "forced", limit: 1 },
      {
        source: "web",
        providerIds: ["web/default"],
        runtimePolicy: { browserMode: "extension" }
      }
    );

    expect(fetchMock).not.toHaveBeenCalled();
    expect(result.ok).toBe(false);
    expect(result.failures[0]?.error).toMatchObject({
      code: "unavailable",
      reasonCode: "env_limited",
      details: {
        browserTransportRequired: true
      }
    });
  });

  it("propagates non-completed forced browser transport results", async () => {
    vi.resetModules();
    const fetchMock = vi.fn(async () => ({
      status: 200,
      url: "https://duckduckgo.com/html/?q=forced&ia=web",
      text: async () => "<html><body>direct fetch should not run</body></html>"
    })) as unknown as typeof fetch;
    const resolve = vi.fn(async () => ({
      ok: false,
      reasonCode: "challenge_detected" as const,
      mode: "extension" as const,
      details: {
        message: "Browser transport preserved a provider challenge."
      }
    }));
    vi.stubGlobal("fetch", fetchMock);

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime({}, { browserFallbackPort: { resolve } });
    const result = await runtime.search(
      { query: "forced", limit: 1 },
      {
        source: "web",
        providerIds: ["web/default"],
        runtimePolicy: {
          browserMode: "extension",
          useCookies: true,
          cookiePolicyOverride: "required"
        }
      }
    );

    expect(fetchMock).not.toHaveBeenCalled();
    expect(result.ok).toBe(false);
    expect(result.failures[0]?.error).toMatchObject({
      code: "unavailable",
      reasonCode: "challenge_detected",
      message: "Browser transport preserved a provider challenge.",
      details: {
        disposition: "failed",
        browserFallbackMode: "extension"
      }
    });
  });

  it("rejects completed browser transport output without html", async () => {
    vi.resetModules();
    const resolve = vi.fn(async (request: { url?: string }) => ({
      ok: true,
      reasonCode: "auth_required" as const,
      mode: "extension" as const,
      output: {
        url: request.url ?? "https://duckduckgo.com/html/?q=forced&ia=web"
      }
    }));

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime({}, { browserFallbackPort: { resolve } });
    const result = await runtime.search(
      { query: "forced", limit: 1 },
      {
        source: "web",
        providerIds: ["web/default"],
        runtimePolicy: {
          browserMode: "extension",
          useCookies: true,
          cookiePolicyOverride: "required"
        }
      }
    );

    expect(result.ok).toBe(false);
    expect(result.failures[0]?.error).toMatchObject({
      code: "auth",
      reasonCode: "auth_required",
      details: {
        fallbackOutputReason: "missing_or_empty_html"
      }
    });
  });

  it("rejects completed browser transport output without usable body content", async () => {
    vi.resetModules();
    const resolve = vi.fn(async (request: { url?: string }) => ({
      ok: true,
      reasonCode: "env_limited" as const,
      mode: "extension" as const,
      output: {
        url: request.url ?? "https://duckduckgo.com/html/?q=forced&ia=web",
        html: "<html><head><title>Shell</title></head>search results continue privacy policy</html>"
      }
    }));

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime({}, { browserFallbackPort: { resolve } });
    const result = await runtime.search(
      { query: "forced", limit: 1 },
      {
        source: "web",
        providerIds: ["web/default"],
        runtimePolicy: {
          browserMode: "extension"
        }
      }
    );

    expect(result.ok).toBe(false);
    expect(result.failures[0]?.error).toMatchObject({
      code: "unavailable",
      reasonCode: "env_limited",
      details: {
        fallbackOutputReason: "empty_extracted_content"
      }
    });
  });

  it("uses completed browser transport output with usable links when browser mode is forced", async () => {
    vi.resetModules();
    const fetchMock = vi.fn(async () => {
      throw new Error("direct fetch should not run for forced browser transport");
    });
    vi.stubGlobal("fetch", fetchMock);
    const resolve = vi.fn(async (request: { url?: string }) => ({
      ok: true,
      reasonCode: "auth_required" as const,
      mode: "extension" as const,
      output: {
        url: request.url ?? "https://duckduckgo.com/html/?q=forced&ia=web",
        html: [
          "<html><body><main>Signed-in search results for browser automation providers.</main>",
          "<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Window/open\">Window open reference</a>",
          "</body></html>"
        ].join("")
      }
    }));

    const { createDefaultRuntime } = await import("../src/providers");
    const runtime = createDefaultRuntime({}, { browserFallbackPort: { resolve } });
    const result = await runtime.search(
      { query: "forced", limit: 1 },
      {
        source: "web",
        providerIds: ["web/default"],
        runtimePolicy: {
          browserMode: "extension",
          useCookies: true,
          cookiePolicyOverride: "required"
        }
      }
    );

    expect(fetchMock).not.toHaveBeenCalled();
    expect(result.ok).toBe(true);
    expect(result.records[0]?.url).toBe("https://developer.mozilla.org/en-US/docs/Web/API/Window/open");
    expect(result.records[0]?.attributes).toMatchObject({
      browser_fallback_mode: "extension",
      browser_fallback_reason_code: "auth_required"
    });
    expect(resolve).toHaveBeenCalledWith(expect.objectContaining({
      provider: "web/default",
      source: "web",
      operation: "search",
      reasonCode: "auth_required"
    }));
  });
});
