import { describe, it, expect, vi, beforeEach } from "vitest";
import fastify from "fastify";
import {
  registerContactRoutes,
  registerAdsRoutes,
  registerPrivacyRoutes,
} from "../misc-routes.js";
import { makeCtx } from "./helpers.js";

vi.mock("../../server/db-schema.js", () => ({
  ensureWallet: vi.fn(),
}));

vi.mock("../../server/data-fetchers.js", () => ({
  fetchWallet: vi.fn(async () => ({
    points: 200,
    inventory: { hints: 5, undos: 3, replays: 1 },
  })),
}));

vi.mock("../../server/user-service.js", () => ({
  getUserById: vi.fn(async (_db: any, userId: number) => ({
    id: userId,
    email: "player@test.com",
    displayName: "Player",
    guest: false,
  })),
}));

vi.mock("../../server/email-service.js", () => ({
  buildContactEmail: vi.fn(() => ({
    subject: "Test",
    text: "Hello",
    html: "<p>Hello</p>",
  })),
}));

vi.mock("../../server/admin-service.js", () => ({
  recordAudit: vi.fn(),
  recordWalletChange: vi.fn(),
}));

const makeAuthCtx = (
  userId = 42,
  opts: {
    adsEnabled?: boolean;
    execute?: (query: string, params?: unknown[]) => Promise<unknown>;
  } = {},
) => {
  const executeFn = vi.fn(
    opts.execute ?? (async () => Promise.resolve([[]] as unknown)),
  );
  return makeCtx({
    db: { execute: executeFn } as any,
    requireAuth: async (request: any) => {
      request.user = { sub: userId };
    },
    config: {
      ads: { enabled: opts.adsEnabled ?? false },
      smtp: { fromEmail: "noreply@test.com", fromName: "Test" },
    } as any,
    mailer: {
      sendMail: vi.fn(),
    } as any,
  });
};

const makeConsentDb = () => {
  let consentRow: Record<string, unknown> | null = null;
  const execute = async (query: string, params?: unknown[]) => {
    if (query.includes("SELECT ads_consent FROM user_privacy_consents")) {
      return [[consentRow ? { ads_consent: consentRow.ads_consent } : undefined].filter(Boolean)];
    }
    if (query.includes("FROM user_privacy_consents")) {
      return [[consentRow].filter(Boolean)];
    }
    if (query.includes("INSERT INTO user_privacy_consents")) {
      const values = Array.isArray(params) ? params : [];
      consentRow = {
        user_id: values[0],
        consent_version: values[1],
        consent_status: values[2],
        ads_consent: values[3],
        personalized_ads_consent: values[4],
        analytics_consent: values[5],
        consent_source: values[6],
        granted_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      };
      return [{ affectedRows: 1 }];
    }
    return [[]];
  };

  return {
    execute,
    setConsent: (enabled: boolean) => {
      consentRow = {
        consent_version: "2026-02",
        consent_status: enabled ? "accepted" : "rejected",
        ads_consent: enabled ? 1 : 0,
        personalized_ads_consent: enabled ? 1 : 0,
        analytics_consent: enabled ? 1 : 0,
        consent_source: "app",
        granted_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      };
    },
  };
};

describe("contact-routes", () => {
  beforeEach(() => vi.clearAllMocks());

  describe("POST /contact", () => {
    it("envoie un message de contact valide", async () => {
      const app = fastify();
      registerContactRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/contact",
        payload: { message: "Bonjour, j'ai un problème avec le jeu." },
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.ok).toBe(true);
    });

    it("refuse un message trop court (< 10 caractères)", async () => {
      const app = fastify();
      registerContactRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/contact",
        payload: { message: "Salut" },
      });
      expect(res.statusCode).toBe(400);
    });

    it("refuse sans message dans le payload", async () => {
      const app = fastify();
      registerContactRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/contact",
        payload: {},
      });
      expect(res.statusCode).toBe(400);
    });

    it("accepte un sujet optionnel", async () => {
      const app = fastify();
      registerContactRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/contact",
        payload: {
          subject: "Bug signalé",
          message: "Il y a un bug sur le niveau 5 facile",
        },
      });
      expect(res.statusCode).toBe(200);
    });
  });
});

describe("ads-routes", () => {
  beforeEach(() => vi.clearAllMocks());

  describe("GET /ads/daily/:userId (ads désactivées)", () => {
    it("retourne un état neutre en 200 quand les pubs sont désactivées", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42, { adsEnabled: false }));

      const res = await app.inject({ method: "GET", url: "/ads/daily/42" });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.adsEnabled).toBe(false);
      expect(body.remainingAds).toBe(0);
    });
  });

  describe("GET /ads/daily/:userId (ads activées)", () => {
    it("retourne le status quand authentifié", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42, { adsEnabled: true }));

      const res = await app.inject({ method: "GET", url: "/ads/daily/42" });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.maxDailyAds).toBe(10);
    });

    it("retourne 403 si userId ne correspond pas", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42, { adsEnabled: true }));

      const res = await app.inject({ method: "GET", url: "/ads/daily/99" });
      expect(res.statusCode).toBe(403);
    });
  });

  describe("POST /ads/daily/reward (ads désactivées)", () => {
    it("retourne 503 quand les pubs sont désactivées", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42, { adsEnabled: false }));

      const res = await app.inject({
        method: "POST",
        url: "/ads/daily/reward",
        payload: { userId: 42 },
      });
      expect(res.statusCode).toBe(503);
    });
  });

  describe("POST /ads/double-points", () => {
    it("refuse si payload invalide", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/ads/double-points",
        payload: { userId: 42 },
      });
      expect(res.statusCode).toBe(400);
    });

    it("refuse basePoints hors limites", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/ads/double-points",
        payload: { userId: 42, basePoints: 200 },
      });
      expect(res.statusCode).toBe(400);
    });

    it("refuse si userId ne correspond pas", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42));

      const res = await app.inject({
        method: "POST",
        url: "/ads/double-points",
        payload: { userId: 99, basePoints: 10 },
      });
      expect(res.statusCode).toBe(403);
    });
  });

  describe("POST /ads/free-solution (ads désactivées)", () => {
    it("retourne 503 quand les pubs sont désactivées", async () => {
      const app = fastify();
      registerAdsRoutes(app, makeAuthCtx(42, { adsEnabled: false }));

      const res = await app.inject({
        method: "POST",
        url: "/ads/free-solution",
        payload: { userId: 42 },
      });
      expect(res.statusCode).toBe(503);
    });
  });

  describe("consentement pub", () => {
    it("refuse la recompense quotidienne sans consentement", async () => {
      const consentDb = makeConsentDb();
      const app = fastify();
      registerAdsRoutes(
        app,
        makeAuthCtx(42, { adsEnabled: true, execute: consentDb.execute }),
      );

      const res = await app.inject({
        method: "POST",
        url: "/ads/daily/reward",
        payload: { userId: 42 },
      });
      expect(res.statusCode).toBe(403);
    });

    it("refuse sans consentement publicitaire", async () => {
      const consentDb = makeConsentDb();
      const app = fastify();
      registerAdsRoutes(
        app,
        makeAuthCtx(42, { adsEnabled: true, execute: consentDb.execute }),
      );

      const res = await app.inject({
        method: "POST",
        url: "/ads/free-solution",
        payload: { userId: 42 },
      });
      expect(res.statusCode).toBe(403);
    });

    it("autorise si consentement publicitaire actif", async () => {
      const consentDb = makeConsentDb();
      consentDb.setConsent(true);
      const app = fastify();
      registerAdsRoutes(
        app,
        makeAuthCtx(42, { adsEnabled: true, execute: consentDb.execute }),
      );

      const res = await app.inject({
        method: "POST",
        url: "/ads/free-solution",
        payload: { userId: 42 },
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.ok).toBe(true);
    });
  });
});

describe("privacy-routes", () => {
  beforeEach(() => vi.clearAllMocks());

  it("retourne pending par défaut si aucun choix", async () => {
    const consentDb = makeConsentDb();
    const app = fastify();
    registerPrivacyRoutes(
      app,
      makeAuthCtx(42, { execute: consentDb.execute, adsEnabled: true }),
    );

    const res = await app.inject({
      method: "GET",
      url: "/privacy/consent/42",
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    expect(body.consent.hasChoice).toBe(false);
    expect(body.consent.adsConsent).toBe(false);
  });

  it("enregistre et retourne le consentement", async () => {
    const consentDb = makeConsentDb();
    const app = fastify();
    registerPrivacyRoutes(
      app,
      makeAuthCtx(42, { execute: consentDb.execute, adsEnabled: true }),
    );

    const updateRes = await app.inject({
      method: "PUT",
      url: "/privacy/consent/42",
      payload: {
        consentVersion: "2026-02",
        adsConsent: true,
        personalizedAdsConsent: true,
        analyticsConsent: false,
      },
    });
    expect(updateRes.statusCode).toBe(200);
    const updatedBody = JSON.parse(updateRes.body);
    expect(updatedBody.consent.hasChoice).toBe(true);
    expect(updatedBody.consent.consentStatus).toBe("custom");
    expect(updatedBody.consent.adsConsent).toBe(true);
    expect(updatedBody.consent.personalizedAdsConsent).toBe(true);
    expect(updatedBody.consent.analyticsConsent).toBe(false);

    const readRes = await app.inject({
      method: "GET",
      url: "/privacy/consent/42",
    });
    expect(readRes.statusCode).toBe(200);
    const readBody = JSON.parse(readRes.body);
    expect(readBody.consent.hasChoice).toBe(true);
    expect(readBody.consent.consentStatus).toBe("custom");
  });
});
