import assert from 'node:assert/strict';
import { randomBytes, randomUUID } from 'node:crypto';
import { after, before, describe, test } from 'node:test';

import { hash } from 'bcryptjs';
import { Hono } from 'hono';
import type { Pool } from 'pg';

import type { AuthService } from './service.js';
import { verifyPassword } from './password.js';

process.env.NODE_ENV = 'test';
process.env.AUTH_ACCESS_TOKEN_SECRET = `test-access-${randomBytes(32).toString('hex')}`;
process.env.AUTH_REFRESH_TOKEN_SECRET = `test-refresh-${randomBytes(32).toString('hex')}`;
process.env.AUTH_ACCESS_TOKEN_TTL_SECONDS = '120';
process.env.AUTH_REFRESH_TOKEN_TTL_SECONDS = '3600';

const testId = randomUUID();
const userId = randomUUID();
const resellerUserId = randomUUID();
const duplicateProfileId = randomUUID();
const companyId = `__auth_test_company__:${testId}`;
const email = `${testId}@auth-test.tonline.invalid`;
const password = `Local-${testId}`;
const resellerEmail = `${testId}-reseller@auth-test.tonline.invalid`;
const resellerProfileKey = `revendedor:${resellerUserId}`;
const profileKeys = [
  `usuario:${userId}`,
  `usuario:${duplicateProfileId}`,
  `empresa:${companyId}`,
];

let pool: Pool;
let service: AuthService;
let app: Hono;

async function responseJson(response: Response): Promise<Record<string, any>> {
  return (await response.json()) as Record<string, any>;
}

async function countTestProfiles(): Promise<number> {
  const result = await pool.query<{ count: string }>(
    `SELECT count(*)::text AS count
     FROM public.kv_store_7249dcd9
     WHERE key = ANY($1::text[])`,
    [profileKeys.slice(0, 2)],
  );
  return Number(result.rows[0]?.count ?? 0);
}

describe('local authentication against PostgreSQL', { concurrency: false }, () => {
  before(async () => {
    const [{ db }, serviceModule, routesModule] = await Promise.all([
      import('../db.js'),
      import('./service.js'),
      import('./routes.js'),
    ]);
    pool = db;
    service = new serviceModule.AuthService(pool);

    const schemaResult = await pool.query<{ users_table: string | null }>(
      `SELECT to_regclass('app_auth.users')::text AS users_table`,
    );
    assert.equal(
      schemaResult.rows[0]?.users_table,
      'app_auth.users',
      'Run the local database migrations before the authentication tests.',
    );

    const encryptedPassword = (await hash(password, 4)).replace('$2b$', '$2a$');
    await pool.query(
      `INSERT INTO app_auth.users
         (id, aud, role, email, encrypted_password, email_confirmed_at,
          confirmed_at, raw_app_meta_data, raw_user_meta_data, created_at,
          updated_at, is_sso_user, is_anonymous, source_record)
       VALUES
         ($1, 'authenticated', 'authenticated', $2, $3,
          transaction_timestamp(), transaction_timestamp(),
          '{"provider":"email","providers":["email"]}'::jsonb,
          $4::jsonb, transaction_timestamp(), transaction_timestamp(),
          false, false, '{"test_record":true}'::jsonb)`,
      [
        userId,
        email,
        encryptedPassword,
        JSON.stringify({ nome: 'Metadata Name', empresa_id: companyId }),
      ],
    );
    await pool.query(
      `INSERT INTO app_auth.users
         (id, aud, role, email, encrypted_password, email_confirmed_at,
          confirmed_at, raw_app_meta_data, raw_user_meta_data, created_at,
          updated_at, is_sso_user, is_anonymous, source_record)
       VALUES
         ($1, 'authenticated', 'authenticated', $2, $3,
          transaction_timestamp(), transaction_timestamp(),
          '{"provider":"email","providers":["email"]}'::jsonb,
          $4::jsonb, transaction_timestamp(), transaction_timestamp(),
          false, false, '{"test_record":true}'::jsonb)`,
      [
        resellerUserId,
        resellerEmail,
        encryptedPassword,
        JSON.stringify({ nome: 'Test Reseller', tipo: 'revendedor' }),
      ],
    );
    await pool.query(
      `INSERT INTO public.kv_store_7249dcd9 (key, value)
       VALUES
         ($1, $2::jsonb),
         ($3, $4::jsonb),
         ($5, $6::jsonb)`,
      [
        profileKeys[0],
        JSON.stringify({
          id: userId,
          authUserId: userId,
          email,
          nome: 'Canonical Test User',
          empresaId: companyId,
          nivel: 'admin_empresa',
          ativo: true,
          permissoes: { configurarEmpresa: true },
          modulosPermitidos: ['faturacao'],
        }),
        profileKeys[1],
        JSON.stringify({
          id: duplicateProfileId,
          authUserId: userId,
          email,
          nome: 'Duplicate Test User',
          empresaId: companyId,
          nivel: 'usuario',
          ativo: true,
        }),
        profileKeys[2],
        JSON.stringify({
          id: companyId,
          nome: 'Auth Test Company, Lda',
          nomeComercial: 'Auth Test Company',
        }),
      ],
    );
    await pool.query(
      `INSERT INTO public.kv_store_7249dcd9 (key, value)
       VALUES ($1, $2::jsonb)`,
      [
        resellerProfileKey,
        JSON.stringify({
          id: resellerUserId,
          authUserId: resellerUserId,
          email: resellerEmail,
          nome: 'Test Reseller',
          tipo: 'revendedor',
          nivel: 'revendedor',
          ativo: true,
        }),
      ],
    );

    app = new Hono();
    app.route('/auth', routesModule.createAuthRoutes(service));
  });

  after(async () => {
    if (!pool) {
      return;
    }

    try {
      await pool.query('DELETE FROM app_auth.users WHERE id = ANY($1::uuid[])', [
        [userId, resellerUserId],
      ]);
      await pool.query(
        `DELETE FROM public.kv_store_7249dcd9
         WHERE key = ANY($1::text[]) OR key = $2`,
        [profileKeys, resellerProfileKey],
      );
    } finally {
      await pool.end();
    }
  });

  test('accepts Supabase bcrypt versions without changing the imported hash', async () => {
    const importedHash = (await hash(password, 4)).replace('$2b$', '$2y$');
    assert.equal(await verifyPassword(password, importedHash), true);
    assert.equal(await verifyPassword('wrong-password', importedHash), false);
    assert.equal(await verifyPassword(password, 'not-a-bcrypt-hash'), false);
  });

  test('rejects invalid credentials without creating a session', async () => {
    const response = await app.request('/auth/signin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password: 'wrong-password' }),
    });
    const body = await responseJson(response);

    assert.equal(response.status, 401);
    assert.equal(body.success, false);
    assert.equal(body.code, 'invalid_credentials');

    const sessions = await pool.query<{ count: string }>(
      `SELECT count(*)::text AS count
       FROM app_auth.sessions
       WHERE user_id = $1`,
      [userId],
    );
    assert.equal(Number(sessions.rows[0]?.count), 0);
  });

  test('signs in an auth user backed only by a reseller profile', async () => {
    const response = await app.request('/auth/signin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: resellerEmail, password }),
    });
    const body = await responseJson(response);

    assert.equal(response.status, 200);
    assert.equal(body.success, true);
    assert.equal(body.data.authUserId, resellerUserId);
    assert.equal(body.data.nome, 'Test Reseller');
    assert.equal(body.data.tipo, 'revendedor');

    await app.request('/auth/signout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-User-Token': body.data.accessToken,
      },
      body: JSON.stringify({ refreshToken: body.data.refreshToken }),
    });
  });

  test('signs in, resolves the canonical authUserId profile and stores only a refresh hash', async () => {
    const response = await app.request('/auth/signin', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': 'auth-integration-test',
      },
      body: JSON.stringify({ email: email.toUpperCase(), password }),
    });
    const body = await responseJson(response);

    assert.equal(response.status, 200);
    assert.equal(body.success, true);
    assert.equal(body.data.userId, userId);
    assert.equal(body.data.nome, 'Canonical Test User');
    assert.equal(body.data.nomeEmpresa, 'Auth Test Company');
    assert.match(body.data.accessToken, /^[\w-]+\.[\w-]+\.[\w-]+$/);
    assert.match(body.data.refreshToken, /^rt_[A-Za-z0-9_-]{64}$/);
    assert.equal(await countTestProfiles(), 2, 'duplicate profiles must not be deleted');

    const stored = await pool.query<{
      token: string | null;
      token_hash: string | null;
      session_id: string;
    }>(
      `SELECT token, token_hash, session_id
       FROM app_auth.refresh_tokens
       WHERE session_id = $1`,
      [body.data.sessionId],
    );
    assert.equal(stored.rows.length, 1);
    assert.equal(stored.rows[0]?.token, null);
    assert.match(stored.rows[0]?.token_hash ?? '', /^[a-f0-9]{64}$/);
    assert.notEqual(stored.rows[0]?.token_hash, body.data.refreshToken);

    const userResponse = await app.request('/auth/user', {
      headers: {
        Authorization: 'Bearer intentionally-not-a-user-token',
        'X-User-Token': body.data.accessToken,
      },
    });
    const userBody = await responseJson(userResponse);
    assert.equal(userResponse.status, 200);
    assert.equal(userBody.user.authUserId, userId);
    assert.equal(userBody.user.nome, 'Canonical Test User');

    const refreshResponse = await app.request('/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refresh_token: body.data.refreshToken }),
    });
    const refreshed = await responseJson(refreshResponse);
    assert.equal(refreshResponse.status, 200);
    assert.notEqual(refreshed.data.refreshToken, body.data.refreshToken);
    assert.notEqual(refreshed.data.accessToken, body.data.accessToken);

    const rotatedRows = await pool.query<{
      token: string | null;
      token_hash: string;
      revoked: boolean;
      used_at: Date | null;
    }>(
      `SELECT token, token_hash, revoked, used_at
       FROM app_auth.refresh_tokens
       WHERE session_id = $1
       ORDER BY id`,
      [body.data.sessionId],
    );
    assert.equal(rotatedRows.rows.length, 2);
    assert.equal(rotatedRows.rows[0]?.revoked, true);
    assert.notEqual(rotatedRows.rows[0]?.used_at, null);
    assert.equal(rotatedRows.rows[1]?.revoked, false);
    assert.equal(rotatedRows.rows[1]?.token, null);

    const replayResponse = await app.request('/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken: body.data.refreshToken }),
    });
    const replay = await responseJson(replayResponse);
    assert.equal(replayResponse.status, 401);
    assert.equal(replay.code, 'refresh_token_reused');

    const revokedAccessResponse = await app.request('/auth/user', {
      headers: { Authorization: `Bearer ${refreshed.data.accessToken}` },
    });
    assert.equal(revokedAccessResponse.status, 401);

    const activeRows = await pool.query<{ count: string }>(
      `SELECT count(*)::text AS count
       FROM app_auth.refresh_tokens
       WHERE session_id = $1 AND revoked = false`,
      [body.data.sessionId],
    );
    assert.equal(Number(activeRows.rows[0]?.count), 0);
  });

  test('signout revokes the access session and its refresh token', async () => {
    const signInResponse = await app.request('/auth/signin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });
    const signedIn = await responseJson(signInResponse);
    assert.equal(signInResponse.status, 200);

    const signOutResponse = await app.request('/auth/signout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-User-Token': signedIn.data.accessToken,
      },
      body: JSON.stringify({ refreshToken: signedIn.data.refreshToken }),
    });
    assert.equal(signOutResponse.status, 200);

    const userResponse = await app.request('/auth/user', {
      headers: { 'X-User-Token': signedIn.data.accessToken },
    });
    assert.equal(userResponse.status, 401);

    const refreshResponse = await app.request('/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken: signedIn.data.refreshToken }),
    });
    assert.equal(refreshResponse.status, 401);
  });
});
