import fs from "node:fs/promises";
import path from "node:path";
import { PassThrough } from "node:stream";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createPinnedLookup } from "../infra/net/ssrf.js";
import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js";
import { captureEnv } from "../test-utils/env.js";
import { saveMediaSource, setMediaStoreNetworkDepsForTest } from "./store.js";

const homeRootTracker = createSuiteTempRootTracker({
  prefix: "openclaw-home-redirect-",
});
const mockRequest = vi.fn();

function createMockHttpExchange() {
  const res = Object.assign(new PassThrough(), {
    statusCode: 0,
    headers: {} as Record<string, string>,
  });
  const req = {
    on: (event: string, handler: (...args: unknown[]) => void) => {
      if (event === "error") {
        res.on("error", handler);
      }
      return req;
    },
    end: () => undefined,
    destroy: () => res.destroy(),
  } as const;
  return { req, res };
}

function mockRedirectExchange(params: { location?: string }) {
  const { req, res } = createMockHttpExchange();
  res.statusCode = 302;
  res.headers = params.location ? { location: params.location } : {};
  return {
    req,
    send(cb: (value: unknown) => void) {
      setImmediate(() => {
        cb(res as unknown);
        res.end();
      });
    },
  };
}

function mockSuccessfulTextExchange(params: { text: string; contentType: string }) {
  const { req, res } = createMockHttpExchange();
  res.statusCode = 200;
  res.headers = { "content-type": params.contentType };
  return {
    req,
    send(cb: (value: unknown) => void) {
      setImmediate(() => {
        cb(res as unknown);
        res.write(params.text);
        res.end();
      });
    },
  };
}

function getRequestHeaders(callIndex: number): Headers {
  const [, options] = mockRequest.mock.calls[callIndex] as [
    URL,
    { headers?: HeadersInit | Record<string, string> } | undefined,
  ];
  return new Headers(options?.headers);
}

async function expectRedirectSaveResult(params: {
  expectedText: string;
  expectedContentType: string;
  expectedExtension: string;
  headers?: Record<string, string>;
  assertRequests?: () => void;
}) {
  const saved = await saveMediaSource("https://example.com/start", params.headers);
  expect(mockRequest).toHaveBeenCalledTimes(2);
  params.assertRequests?.();
  expect(saved.contentType).toBe(params.expectedContentType);
  expect(path.extname(saved.path)).toBe(params.expectedExtension);
  expect(await fs.readFile(saved.path, "utf8")).toBe(params.expectedText);
  const stat = await fs.stat(saved.path);
  const expectedMode = process.platform === "win32" ? 0o666 : 0o644 & ~process.umask();
  expect(stat.mode & 0o777).toBe(expectedMode);
}

async function expectRedirectSaveFailure(expectedMessage: string) {
  await expect(saveMediaSource("https://example.com/start")).rejects.toThrow(expectedMessage);
  expect(mockRequest).toHaveBeenCalledTimes(1);
}

describe("media store redirects", () => {
  let envSnapshot: ReturnType<typeof captureEnv>;
  let home = "";

  beforeAll(async () => {
    envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
    await homeRootTracker.setup();
    home = await homeRootTracker.make("state");
    process.env.OPENCLAW_STATE_DIR = home;
  });

  beforeEach(() => {
    mockRequest.mockClear();
    setMediaStoreNetworkDepsForTest({
      httpRequest: (...args) => mockRequest(...args),
      httpsRequest: (...args) => mockRequest(...args),
      resolvePinnedHostname: async (hostname) => ({
        hostname,
        addresses: ["93.184.216.34"],
        lookup: createPinnedLookup({ hostname, addresses: ["93.184.216.34"] }),
      }),
    });
  });

  afterAll(async () => {
    await homeRootTracker.cleanup();
    home = "";
    envSnapshot.restore();
    setMediaStoreNetworkDepsForTest();
    vi.clearAllMocks();
  });

  it("follows redirects and keeps detected mime/extension", async () => {
    let call = 0;
    mockRequest.mockImplementation((_url, _opts, cb) => {
      call += 1;
      if (call === 1) {
        const exchange = mockRedirectExchange({ location: "https://example.com/final" });
        exchange.send(cb);
        return exchange.req;
      }

      const exchange = mockSuccessfulTextExchange({
        text: "redirected",
        contentType: "text/plain",
      });
      exchange.send(cb);
      return exchange.req;
    });

    await expectRedirectSaveResult({
      expectedText: "redirected",
      expectedContentType: "text/plain",
      expectedExtension: ".txt",
    });
  });

  it("strips sensitive headers when a redirect crosses origins", async () => {
    let call = 0;
    mockRequest.mockImplementation((_url, _opts, cb) => {
      call += 1;
      if (call === 1) {
        const exchange = mockRedirectExchange({ location: "https://cdn.example.com/final" });
        exchange.send(cb);
        return exchange.req;
      }

      const exchange = mockSuccessfulTextExchange({
        text: "redirected",
        contentType: "text/plain",
      });
      exchange.send(cb);
      return exchange.req;
    });

    await saveMediaSource("https://example.com/start", {
      Authorization: "Bearer secret",
      Cookie: "session=abc",
      "X-Api-Key": "custom-secret",
      Accept: "text/plain",
      "User-Agent": "OpenClaw-Test/1.0",
    });

    expect(mockRequest).toHaveBeenCalledTimes(2);
    const secondHeaders = getRequestHeaders(1);
    expect(secondHeaders.get("authorization")).toBeNull();
    expect(secondHeaders.get("cookie")).toBeNull();
    expect(secondHeaders.get("x-api-key")).toBeNull();
    expect(secondHeaders.get("accept")).toBe("text/plain");
    expect(secondHeaders.get("user-agent")).toBe("OpenClaw-Test/1.0");
  });

  it("keeps headers when a redirect stays on the same origin", async () => {
    let call = 0;
    mockRequest.mockImplementation((_url, _opts, cb) => {
      call += 1;
      if (call === 1) {
        const exchange = mockRedirectExchange({ location: "/final" });
        exchange.send(cb);
        return exchange.req;
      }

      const exchange = mockSuccessfulTextExchange({
        text: "redirected",
        contentType: "text/plain",
      });
      exchange.send(cb);
      return exchange.req;
    });

    await saveMediaSource("https://example.com/start", {
      Authorization: "Bearer secret",
    });

    expect(getRequestHeaders(1).get("authorization")).toBe("Bearer secret");
  });

  it("fails when redirect response omits location header", async () => {
    mockRequest.mockImplementationOnce((_url, _opts, cb) => {
      const exchange = mockRedirectExchange({});
      exchange.send(cb);
      return exchange.req;
    });
    await expectRedirectSaveFailure("Redirect loop or missing Location header");
  });
});
