import type { DestinationStream } from 'pino';
import type pg from 'pg';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { buildApp } from '../build-app.js';
import { createPool } from '../../infrastructure/db/pool.js';
import { createLogger } from '../../infrastructure/logging/logger.js';
import {
  getTestDbUrl,
  recreateDatabase,
  runMigrations,
} from '../../infrastructure/db/tests/db-test-support.js';
import { createClubService, CLUB_DEVICE_COOKIE_NAME } from '../../modules/club/index.js';
import { SESSION_COOKIE_NAME } from '../../modules/identity/index.js';

const hasDb = Boolean(process.env.TEST_DATABASE_URL);

function silentLogger() {
  const destination: DestinationStream = { write: () => undefined };
  return createLogger({ level: 'info', destination });
}

function cookieValue(setCookieHeader: string | string[] | undefined): string {
  const raw = Array.isArray(setCookieHeader) ? setCookieHeader[0] : setCookieHeader;
  expect(raw).toBeDefined();
  const pair = (raw as string).split(';')[0] as string;
  return pair.slice(pair.indexOf('=') + 1);
}

describe.skipIf(!hasDb)('club backend phase 1 (real PostgreSQL)', () => {
  const url = hasDb ? getTestDbUrl() : '';
  let pool: pg.Pool;
  let app: Awaited<ReturnType<typeof buildApp>>;
  let adminCookie = '';

  beforeAll(async () => {
    await recreateDatabase(url);
    await runMigrations(url, 'up');
    pool = createPool(url);
    app = await buildApp({ logger: silentLogger(), pool, cookieSecure: true });

    const user = { email: 'club-admin@example.com', password: 'correct horse battery staple' };
    const registered = await app.inject({
      method: 'POST',
      url: '/auth/register',
      headers: { 'content-type': 'application/json' },
      payload: user,
    });
    const id = (registered.json() as { id: string }).id;
    await pool.query(
      `UPDATE identity_users
          SET role = $1,
              email_confirmed = true,
              confirmed_at = now(),
              confirmation_token = null
        WHERE id = $2`,
      ['admin', id],
    );
    const login = await app.inject({
      method: 'POST',
      url: '/auth/login',
      headers: { 'content-type': 'application/json' },
      payload: user,
    });
    adminCookie = cookieValue(login.headers['set-cookie']);
  });

  afterAll(async () => {
    await app?.close();
    await pool?.end();
  });

  it('creates an anonymous member, reuses device identity and lists movements', async () => {
    const config = await app.inject({ method: 'GET', url: '/club/config' });
    expect(config.statusCode).toBe(200);
    expect(config.json()).toMatchObject({ clubEnabled: true, cashbackBps: 200 });

    const joined = await app.inject({ method: 'POST', url: '/club/join' });
    expect(joined.statusCode).toBe(201);
    expect(String(joined.headers['set-cookie'])).toContain(`${CLUB_DEVICE_COOKIE_NAME}=`);
    const payload = joined.json() as {
      created: boolean;
      deviceToken: string;
      member: { id: string; memberCode: string; currentBalanceCents: number };
    };
    expect(payload.created).toBe(true);
    expect(payload.deviceToken).toMatch(/^[A-Za-z0-9_-]{40,}$/);
    expect(payload.member.memberCode).toMatch(/^MDV-[A-Z0-9]{8}$/);
    expect(payload.member.currentBalanceCents).toBe(0);

    const me = await app.inject({
      method: 'GET',
      url: '/club/me',
      headers: { 'x-club-device-token': payload.deviceToken },
    });
    expect(me.statusCode).toBe(200);
    expect(me.json()).toMatchObject({ member: { id: payload.member.id, memberCode: payload.member.memberCode } });

    const repeatedJoin = await app.inject({
      method: 'POST',
      url: '/club/join',
      cookies: { [CLUB_DEVICE_COOKIE_NAME]: payload.deviceToken },
    });
    expect(repeatedJoin.statusCode).toBe(200);
    expect(repeatedJoin.json()).toMatchObject({ created: false, member: { id: payload.member.id } });

    const club = createClubService(pool);
    const first = await club.recordTransaction({
      memberId: payload.member.id,
      type: 'bonus',
      amountCents: 100,
      balanceDeltaCents: 100,
      idempotencyKey: 'club-bonus-1',
      metadata: { reason: 'welcome' },
    });
    expect(first.created).toBe(true);
    expect(first.member.currentBalanceCents).toBe(100);

    const replay = await club.recordTransaction({
      memberId: payload.member.id,
      type: 'bonus',
      amountCents: 100,
      balanceDeltaCents: 100,
      idempotencyKey: 'club-bonus-1',
      metadata: { reason: 'welcome' },
    });
    expect(replay.created).toBe(false);
    expect(replay.member.currentBalanceCents).toBe(100);

    const movements = await app.inject({
      method: 'GET',
      url: '/club/movements?limit=10',
      headers: { 'x-club-device-token': payload.deviceToken },
    });
    expect(movements.statusCode).toBe(200);
    expect(movements.json()).toMatchObject({
      member: { id: payload.member.id, currentBalanceCents: 100 },
      items: [
        {
          type: 'bonus',
          amountCents: 100,
          balanceDeltaCents: 100,
          idempotencyKey: 'club-bonus-1',
        },
      ],
    });
  });

  it('reads and updates admin club settings and blocks new joins when disabled', async () => {
    const current = await app.inject({
      method: 'GET',
      url: '/admin/club/settings',
      cookies: { [SESSION_COOKIE_NAME]: adminCookie },
    });
    expect(current.statusCode).toBe(200);
    expect(current.json()).toMatchObject({
      clubEnabled: true,
      cashbackBps: 200,
      cashbackPercentage: 2,
      allowAnonymousMembers: true,
      allowRecoveryCodes: true,
      minimumRedeemAmountCents: 500,
    });

    const updated = await app.inject({
      method: 'PATCH',
      url: '/admin/club/settings',
      headers: { 'content-type': 'application/json' },
      cookies: { [SESSION_COOKIE_NAME]: adminCookie },
      payload: {
        clubEnabled: false,
        cashbackPercentage: 3.5,
        allowAnonymousMembers: false,
        allowRecoveryCodes: false,
        minimumRedeemAmountCents: 700,
      },
    });
    expect(updated.statusCode).toBe(200);
    expect(updated.json()).toMatchObject({
      clubEnabled: false,
      cashbackBps: 350,
      cashbackPercentage: 3.5,
      allowAnonymousMembers: false,
      allowRecoveryCodes: false,
      minimumRedeemAmountCents: 700,
    });

    const blocked = await app.inject({ method: 'POST', url: '/club/join' });
    expect(blocked.statusCode).toBe(409);
    expect(blocked.json()).toMatchObject({ error: { code: 'CLUB_DISABLED' } });
  });
});
