import { afterEach, describe, expect, it, vi } from "vitest";
import type { BrowserManagerLike } from "../src/browser/manager-types";
import { createBrowserFallbackPort } from "../src/providers/runtime-factory";
import type { BrowserFallbackPort, ProviderAdapter } 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 default and requested runtime timeouts through provider context", async () => {
    vi.resetModules();
    const { DEFAULT_PROVIDER_BUDGETS, ProviderRuntime } = await import("../src/providers");
    const capturedTimeouts: number[] = [];
    const provider: ProviderAdapter = {
      id: "web/timeout-context",
      source: "web",
      search: vi.fn(async (_input, context) => {
        capturedTimeouts.push(context.timeoutMs);
        return [{
          id: `timeout-${capturedTimeouts.length}`,
          source: "web",
          provider: "web/timeout-context",
          url: `https://example.com/timeout-${capturedTimeouts.length}`,
          title: "Timeout context",
          content: "Timeout context record",
          timestamp: "2026-03-23T00:00:00.000Z",
          confidence: 0.8,
          attributes: {}
        }];
      }),
      capabilities: () => ({
        providerId: "web/timeout-context",
        source: "web",
        operations: {
          search: { op: "search", supported: true },
          fetch: { op: "fetch", supported: false },
          crawl: { op: "crawl", supported: false },
          post: { op: "post", supported: false }
        },
        policy: {
          posting: "unsupported",
          riskNoticeRequired: false,
          confirmationRequired: false
        },
        metadata: {}
      })
    };
    const runtime = new ProviderRuntime();
    runtime.register(provider);

    await expect(runtime.search(
      { query: "default timeout" },
      { source: "web", providerIds: ["web/timeout-context"] }
    )).resolves.toMatchObject({ ok: true });
    await expect(runtime.search(
      { query: "short timeout" },
      { source: "web", providerIds: ["web/timeout-context"], timeoutMs: 25 }
    )).resolves.toMatchObject({ ok: true });

    expect(capturedTimeouts).toEqual([DEFAULT_PROVIDER_BUDGETS.timeoutMs.search, 25]);
  });

  it("maps rate-limited runtime fallback errors without explicit reason codes to env_limited", 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: "rate_limited",
        message: "synthetic rate limit without reason",
        retryable: true
      }))
    }));

    vi.stubGlobal("fetch", vi.fn(async () => {
      throw new Error("ignored rate limit 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 rate limit 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("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("maps user-owned Google auth intent to forced extension transport without changing defaults", async () => {
    vi.resetModules();
    const {
      parseGoogleAuthIntent,
      serializeGoogleAuthIntent
    } = await import("../src/core/auth-intent");
    const { resolveProviderRuntimePolicy } = await import("../src/providers/runtime-policy");

    expect(parseGoogleAuthIntent(undefined)).toBe("none");
    expect(parseGoogleAuthIntent("user-owned")).toBe("user_owned_google");
    expect(parseGoogleAuthIntent("user_owned_google")).toBe("user_owned_google");
    expect(serializeGoogleAuthIntent("user_owned_google")).toBe("user-owned");
    expect(serializeGoogleAuthIntent("none")).toBe("none");
    expect(() => parseGoogleAuthIntent("daily-google")).toThrow("Unsupported Google auth intent");

    expect(resolveProviderRuntimePolicy({ source: "web" }).browser).toEqual({
      preferredModes: ["managed_headed"],
      forceTransport: false
    });

    expect(resolveProviderRuntimePolicy({
      source: "web",
      runtimePolicy: { googleAuthIntent: "user_owned_google" }
    }).browser).toEqual({
      preferredModes: ["extension"],
      forceTransport: true
    });
  });

  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"
    }));
  });
  it("classifies default fetched auth and challenge walls without browser recovery", async () => {
    vi.resetModules();
    const responses = [
      {
        status: 401,
        html: "<html><head><title>Login required</title></head><body>Please sign in to continue to protected results.</body></html>"
      },
      {
        status: 503,
        html: "<html><head><title>Please complete captcha challenge</title></head><body>Checking your browser before accessing this site.</body></html>"
      }
    ];
    vi.stubGlobal("fetch", vi.fn(async (input: string | URL) => {
      const response = responses.shift();
      return {
        status: response?.status ?? 200,
        url: String(input),
        text: async () => response?.html ?? "<html><body>plain result</body></html>"
      };
    }) as unknown as typeof fetch);

    const { createDefaultRuntime } = await import("../src/providers");
    const authResult = await createDefaultRuntime().search(
      { query: "private docs", limit: 1 },
      { source: "web", providerIds: ["web/default"] }
    );
    const challengeResult = await createDefaultRuntime().search(
      { query: "captcha docs", limit: 1 },
      { source: "web", providerIds: ["web/default"] }
    );

    expect(authResult.ok).toBe(false);
    expect(authResult.failures[0]?.error).toMatchObject({
      code: "auth",
      reasonCode: "token_required",
      message: expect.stringContaining("Authentication required")
    });
    expect(challengeResult.ok).toBe(false);
    expect(challengeResult.failures[0]?.error).toMatchObject({
      code: "unavailable",
      reasonCode: "challenge_detected"
    });
  });


  it("preserves anti-bot challenges reported by fallback status metadata", async () => {
    vi.resetModules();
    const cleanHtml = "<html><body><main>Clean fallback content without blocker text.</main></body></html>";
    const manager = {
      launch: vi.fn(async () => ({ sessionId: "session-anti-bot-status" })),
      goto: vi.fn(async () => undefined),
      waitForLoad: vi.fn(async () => undefined),
      withPage: vi.fn(async (_sessionId, _ref, callback) => {
        return await callback({
          content: async () => cleanHtml,
          waitForTimeout: async () => undefined
        });
      }),
      status: vi.fn(async () => ({
        sessionId: "session-anti-bot-status",
        mode: "managed",
        url: "https://example.com/search?q=browser",
        meta: {
          blockerState: "challenge",
          challenge: {
            blockerType: "anti_bot_challenge",
            status: "active"
          }
        }
      })),
      disconnect: vi.fn(async () => undefined)
    } satisfies Partial<BrowserManagerLike>;

    const port = createBrowserFallbackPort(manager as BrowserManagerLike, { policy: "off" });
    const result = await port?.resolve({
      provider: "web/default",
      source: "web",
      operation: "search",
      reasonCode: "env_limited",
      url: "https://example.com/search?q=browser",
      preferredModes: ["managed_headed"],
      captureDelayMs: 0,
      settleTimeoutMs: 1
    });

    expect(result?.ok).toBe(false);
    expect(result?.reasonCode).toBe("challenge_detected");
    expect(result?.disposition).toBe("challenge_preserved");
    expect(result?.challenge?.blockerType).toBe("anti_bot_challenge");
  });

});
