// Learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

// Set up encryption key for tests (64 hex chars = 32 bytes)
process.env.ENCRYPTION_KEY =
  process.env.ENCRYPTION_KEY ||
  "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";

// Polyfill setImmediate for jsdom environment (needed by nodemailer)
if (typeof setImmediate === "undefined") {
  global.setImmediate = (fn, ...args) => setTimeout(fn, 0, ...args);
  global.clearImmediate = (id) => clearTimeout(id);
}

// Polyfill TextEncoder/TextDecoder for jose library
const { TextEncoder, TextDecoder } = require("util");
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

// Mock Next.js router
jest.mock("next/navigation", () => ({
  useRouter() {
    return {
      push: jest.fn(),
      replace: jest.fn(),
      prefetch: jest.fn(),
      back: jest.fn(),
      pathname: "/",
      query: {},
      asPath: "/",
    };
  },
  usePathname() {
    return "/";
  },
  useSearchParams() {
    return new URLSearchParams();
  },
  useParams() {
    return { lang: "en" };
  },
  redirect: jest.fn(),
  notFound: jest.fn(),
}));

// Mock next/image
jest.mock("next/image", () => ({
  __esModule: true,
  default: (props) => {
    // eslint-disable-next-line jsx-a11y/alt-text
    return <img {...props} />;
  },
}));

// Setup window.matchMedia mock (only in jsdom environment)
if (typeof window !== "undefined")
  Object.defineProperty(window, "matchMedia", {
    writable: true,
    value: jest.fn().mockImplementation((query) => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: jest.fn(),
      removeListener: jest.fn(),
      addEventListener: jest.fn(),
      removeEventListener: jest.fn(),
      dispatchEvent: jest.fn(),
    })),
  });

// Mock IntersectionObserver (only in jsdom environment)
if (typeof window !== "undefined") {
  global.IntersectionObserver = class IntersectionObserver {
    constructor() {}
    disconnect() {}
    observe() {}
    takeRecords() {
      return [];
    }
    unobserve() {}
  };
}

// Polyfill Next.js Web APIs for tests
// These are needed because some imports happen at module load time

// Mock fetch globally
global.fetch = jest.fn(() =>
  Promise.resolve({
    ok: true,
    status: 200,
    json: () => Promise.resolve({}),
    text: () => Promise.resolve(""),
    blob: () => Promise.resolve(new Blob()),
  }),
);

// Polyfill Request/Response/Headers from Node.js 18+ or mock them
try {
  if (typeof Request === "undefined") {
    const {
      Request,
      Response,
      Headers,
    } = require("next/dist/compiled/@edge-runtime/primitives/fetch");
    global.Request = Request;
    global.Response = Response;
    global.Headers = Headers;
  }
} catch (e) {
  // Fallback: Create simple mocks if Next.js primitives not available
  global.Request = class Request {
    #url;
    #method;
    #headers;
    #body;

    constructor(url, options = {}) {
      this.#url = typeof url === "string" ? url : url.toString();
      this.#method = options?.method || "GET";
      this.#headers = new Map(Object.entries(options?.headers || {}));
      this.#body = options?.body;
    }

    get url() {
      return this.#url;
    }

    get method() {
      return this.#method;
    }

    get headers() {
      return this.#headers;
    }

    get body() {
      return this.#body;
    }

    async json() {
      if (typeof this.#body === "string") {
        return JSON.parse(this.#body);
      }
      return this.#body;
    }

    async text() {
      return typeof this.#body === "string"
        ? this.#body
        : JSON.stringify(this.#body);
    }
  };

  global.Response = class Response {
    constructor(body, options) {
      this.body = body;
      this.status = options?.status || 200;
      this.ok = this.status >= 200 && this.status < 300;

      // Create proper Headers object
      const headersMap = new Map(Object.entries(options?.headers || {}));
      this.headers = {
        get: (name) =>
          headersMap.get(name) || headersMap.get(name.toLowerCase()) || null,
        set: (name, value) => headersMap.set(name, value),
        append: (name, value) => {
          const existing = headersMap.get(name);
          headersMap.set(name, existing ? `${existing}, ${value}` : value);
        },
        has: (name) =>
          headersMap.has(name) || headersMap.has(name.toLowerCase()),
        delete: (name) => headersMap.delete(name),
        forEach: (callback) => headersMap.forEach(callback),
        keys: () => headersMap.keys(),
        values: () => headersMap.values(),
        entries: () => headersMap.entries(),
      };
    }
    async json() {
      if (typeof this.body === "string") {
        return JSON.parse(this.body);
      }
      return this.body;
    }
    async text() {
      return typeof this.body === "string"
        ? this.body
        : JSON.stringify(this.body);
    }
  };

  // Add static Response.json method (Next.js pattern)
  global.Response.json = function (data, options) {
    const response = new global.Response(JSON.stringify(data), {
      ...options,
      headers: {
        "content-type": "application/json",
        ...(options?.headers || {}),
      },
    });

    // Add cookies API (NextResponse feature)
    response.cookies = {
      set: jest.fn(),
      get: jest.fn(),
      delete: jest.fn(),
      has: jest.fn(),
      clear: jest.fn(),
      getAll: jest.fn(() => []),
    };

    return response;
  };

  global.Headers = class Headers extends Map {
    constructor(init) {
      super(Object.entries(init || {}));
    }

    append(name, value) {
      const existing = this.get(name);
      if (existing) {
        this.set(name, `${existing}, ${value}`);
      } else {
        this.set(name, value);
      }
    }

    delete(name) {
      return super.delete(name);
    }

    entries() {
      return super.entries();
    }

    forEach(callback, thisArg) {
      return super.forEach(callback, thisArg);
    }

    get(name) {
      return super.get(name) || null;
    }

    has(name) {
      return super.has(name);
    }

    keys() {
      return super.keys();
    }

    set(name, value) {
      return super.set(name, value);
    }

    values() {
      return super.values();
    }
  };
}
