/**
 * authServiceDb.test.ts
 *
 * DB-centric unit tests for authService.ts using a real in-memory SQLite database.
 * Pure function tests live in authService.test.ts (stub DB); this file covers
 * functions that require actual DB queries to exercise their logic.
 */

// ---------------------------------------------------------------------------
// vi.hoisted: build the real in-memory DB and the module mock before any import
// ---------------------------------------------------------------------------

const { testDb, dbMock } = vi.hoisted(() => {
  const Database = require('better-sqlite3');
  const db = new Database(':memory:');
  db.exec('PRAGMA journal_mode = WAL');
  db.exec('PRAGMA foreign_keys = ON');
  db.exec('PRAGMA busy_timeout = 5000');
  const mock = {
    db,
    closeDb: () => {},
    reinitialize: () => {},
    canAccessTrip: (tripId: any, userId: number) =>
      db
        .prepare(
          `SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`
        )
        .get(userId, tripId, userId),
    isOwner: (tripId: any, userId: number) =>
      !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId),
  };
  return { testDb: db, dbMock: mock };
});

vi.mock('../../../src/db/database', () => dbMock);
vi.mock('../../../src/config', () => ({
  JWT_SECRET: 'test-secret',
  ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2',
  updateJwtSecret: () => {},
}));
vi.mock('../../../src/services/mfaCrypto', () => ({
  encryptMfaSecret: vi.fn((s) => `enc:${s}`),
  decryptMfaSecret: vi.fn((s: string) => s.replace('enc:', '')),
}));
vi.mock('../../../src/services/apiKeyCrypto', () => ({
  decrypt_api_key: vi.fn((v) => v),
  maybe_encrypt_api_key: vi.fn((v) => v),
  mask_stored_api_key: vi.fn((v: string | null | undefined) => (v ? '••••••••' : null)),
  encrypt_api_key: vi.fn((v) => v),
}));
vi.mock('../../../src/services/permissions', () => ({
  getAllPermissions: vi.fn(() => ({})),
  checkPermission: vi.fn(),
}));
vi.mock('../../../src/services/ephemeralTokens', () => ({ createEphemeralToken: vi.fn() }));
vi.mock('../../../src/mcp', () => ({ revokeUserSessions: vi.fn() }));
vi.mock('../../../src/scheduler', () => ({
  startTripReminders: vi.fn(),
  buildCronExpression: vi.fn(),
  loadSettings: vi.fn(() => ({ enabled: false })),
  VALID_INTERVALS: ['daily', 'weekly', 'monthly'],
}));

// ---------------------------------------------------------------------------
// Imports (after mocks)
// ---------------------------------------------------------------------------

import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';
import { createTables } from '../../../src/db/schema';
import { runMigrations } from '../../../src/db/migrations';
import { resetTestDb } from '../../helpers/test-db';
import { createUser, createAdmin, createInviteToken } from '../../helpers/factories';
import {
  updateSettings,
  getSettings,
  listUsers,
  getAppSettings,
  validateKeys,
  isOidcOnlyMode,
  resolveAuthToggles,
  setupMfa,
  enableMfa,
  disableMfa,
  validateInviteToken,
  registerUser,
  loginUser,
  changePassword,
  verifyMfaLogin,
  createMcpToken,
  deleteMcpToken,
} from '../../../src/services/authService';

// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------

beforeAll(() => {
  createTables(testDb);
  runMigrations(testDb);
});

beforeEach(() => resetTestDb(testDb));

afterAll(() => testDb.close());

// ---------------------------------------------------------------------------
// updateSettings
// ---------------------------------------------------------------------------

describe('updateSettings', () => {
  it('AUTH-DB-001: updates username successfully', () => {
    const { user } = createUser(testDb);
    const result = updateSettings(user.id, { username: 'newname' });
    expect(result.success).toBe(true);
    expect(result.user?.username).toBe('newname');
  });

  it('AUTH-DB-002: returns 400 when username is too short (< 2 chars)', () => {
    const { user } = createUser(testDb);
    const result = updateSettings(user.id, { username: 'x' });
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/between 2 and 50/i);
  });

  it('AUTH-DB-003: returns 400 when username has invalid characters (spaces)', () => {
    const { user } = createUser(testDb);
    const result = updateSettings(user.id, { username: 'bad name' });
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/only contain/i);
  });

  it('AUTH-DB-004: returns 409 when username is already taken by another user', () => {
    const { user: user1 } = createUser(testDb, { username: 'alice' });
    const { user: user2 } = createUser(testDb, { username: 'bob' });
    const result = updateSettings(user2.id, { username: user1.username });
    expect(result.status).toBe(409);
    expect(result.error).toMatch(/already taken/i);
  });

  it('AUTH-DB-005: updates email successfully', () => {
    const { user } = createUser(testDb);
    const result = updateSettings(user.id, { email: 'new@example.com' });
    expect(result.success).toBe(true);
    expect(result.user?.email).toBe('new@example.com');
  });

  it('AUTH-DB-006: returns 400 for invalid email format', () => {
    const { user } = createUser(testDb);
    const result = updateSettings(user.id, { email: 'not-an-email' });
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/invalid email/i);
  });

  it('AUTH-DB-007: returns 409 when email is already taken by another user', () => {
    const { user: user1 } = createUser(testDb, { email: 'taken@example.com' });
    const { user: user2 } = createUser(testDb);
    const result = updateSettings(user2.id, { email: user1.email });
    expect(result.status).toBe(409);
    expect(result.error).toMatch(/already taken/i);
  });

  it('AUTH-DB-008: returns success with no field changes when empty body is passed', () => {
    const { user } = createUser(testDb);
    const result = updateSettings(user.id, {});
    expect(result.success).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// getSettings
// ---------------------------------------------------------------------------

describe('getSettings', () => {
  it('AUTH-DB-009: returns 403 for non-admin user', () => {
    const { user } = createUser(testDb);
    const result = getSettings(user.id);
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/admin/i);
  });

  it('AUTH-DB-010: returns maps_api_key and openweather_api_key for admin', () => {
    const { user } = createAdmin(testDb);
    testDb
      .prepare('UPDATE users SET maps_api_key = ?, openweather_api_key = ? WHERE id = ?')
      .run('maps-key-value', 'weather-key-value', user.id);
    const result = getSettings(user.id);
    expect(result.status).toBeUndefined();
    expect(result.settings).toBeDefined();
    expect(result.settings).toHaveProperty('maps_api_key');
    expect(result.settings).toHaveProperty('openweather_api_key');
  });
});

// ---------------------------------------------------------------------------
// listUsers
// ---------------------------------------------------------------------------

describe('listUsers', () => {
  it('AUTH-DB-011: returns all users except self, sorted by username', () => {
    const { user: self } = createUser(testDb, { username: 'zzself' });
    createUser(testDb, { username: 'alice' });
    createUser(testDb, { username: 'charlie' });
    createUser(testDb, { username: 'bob' });
    const result = listUsers(self.id);
    expect(result).toHaveLength(3);
    const names = result.map((u) => u.username);
    expect(names).toEqual([...names].sort());
    expect(names).not.toContain('zzself');
  });

  it('AUTH-DB-012: returns empty array when only one user exists', () => {
    const { user } = createUser(testDb);
    const result = listUsers(user.id);
    expect(result).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// getAppSettings
// ---------------------------------------------------------------------------

describe('getAppSettings', () => {
  it('AUTH-DB-013: returns 403 for non-admin', () => {
    const { user } = createUser(testDb);
    const result = getAppSettings(user.id);
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/admin/i);
  });

  it('AUTH-DB-014: returns settings object for admin with known key allow_registration', () => {
    const { user } = createAdmin(testDb);
    testDb
      .prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'true')")
      .run();
    const result = getAppSettings(user.id);
    expect(result.status).toBeUndefined();
    expect(result.data).toBeDefined();
    expect(result.data).toHaveProperty('allow_registration', 'true');
  });
});

// ---------------------------------------------------------------------------
// validateKeys
// ---------------------------------------------------------------------------

describe('validateKeys', () => {
  it('AUTH-DB-015: returns 403 for non-admin', async () => {
    const { user } = createUser(testDb);
    const result = await validateKeys(user.id);
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/admin/i);
    expect(result.maps).toBe(false);
    expect(result.weather).toBe(false);
  });

  it('AUTH-DB-016: returns { maps: false, weather: false } when no API keys are stored', async () => {
    const { user } = createAdmin(testDb);
    const result = await validateKeys(user.id);
    expect(result.maps).toBe(false);
    expect(result.weather).toBe(false);
    expect(result.maps_details).toBeNull();
  });

  it('AUTH-DB-017: returns { maps: true } when fetch returns 200', async () => {
    const { user } = createAdmin(testDb);
    testDb.prepare('UPDATE users SET maps_api_key = ? WHERE id = ?').run('test-key', user.id);

    const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce({
      status: 200,
      statusText: 'OK',
      text: async () => '',
    } as Response);

    const result = await validateKeys(user.id);
    expect(result.maps).toBe(true);
    expect(result.maps_details?.ok).toBe(true);

    fetchSpy.mockRestore();
  });

  it('AUTH-DB-018: returns { maps: false } when fetch throws a network error', async () => {
    const { user } = createAdmin(testDb);
    testDb.prepare('UPDATE users SET maps_api_key = ? WHERE id = ?').run('test-key', user.id);

    const fetchSpy = vi
      .spyOn(global, 'fetch')
      .mockRejectedValueOnce(new Error('Network failure'));

    const result = await validateKeys(user.id);
    expect(result.maps).toBe(false);
    expect(result.maps_details?.error_status).toBe('FETCH_ERROR');
    expect(result.maps_details?.error_message).toBe('Network failure');

    fetchSpy.mockRestore();
  });
});

// ---------------------------------------------------------------------------
// isOidcOnlyMode
// ---------------------------------------------------------------------------

describe('isOidcOnlyMode', () => {
  it('AUTH-DB-019: returns false when OIDC_ONLY env var is not set', () => {
    vi.stubEnv('OIDC_ONLY', '');
    expect(isOidcOnlyMode()).toBe(false);
    vi.unstubAllEnvs();
  });

  it('AUTH-DB-020: returns false when OIDC_ONLY=true but no OIDC_ISSUER configured', () => {
    vi.stubEnv('OIDC_ONLY', 'true');
    vi.stubEnv('OIDC_ISSUER', '');
    vi.stubEnv('OIDC_CLIENT_ID', '');
    expect(isOidcOnlyMode()).toBe(false);
    vi.unstubAllEnvs();
  });

  it('AUTH-DB-021: returns true when OIDC_ONLY=true AND OIDC_ISSUER AND OIDC_CLIENT_ID are set', () => {
    vi.stubEnv('OIDC_ONLY', 'true');
    vi.stubEnv('OIDC_ISSUER', 'https://sso.example.com');
    vi.stubEnv('OIDC_CLIENT_ID', 'trek-client');
    expect(isOidcOnlyMode()).toBe(true);
    vi.unstubAllEnvs();
  });
});

// ---------------------------------------------------------------------------
// resolveAuthToggles
// ---------------------------------------------------------------------------

describe('resolveAuthToggles', () => {
  afterEach(() => {
    vi.unstubAllEnvs();
    testDb.prepare("DELETE FROM app_settings WHERE key IN ('password_login','password_registration','oidc_login','oidc_registration','oidc_only','allow_registration')").run();
  });

  it('AUTH-DB-022a: returns all true by default (no DB keys, no env override)', () => {
    vi.stubEnv('OIDC_ONLY', '');
    const t = resolveAuthToggles();
    expect(t.password_login).toBe(true);
    expect(t.password_registration).toBe(true);
    expect(t.oidc_login).toBe(true);
    expect(t.oidc_registration).toBe(true);
  });

  it('AUTH-DB-022b: legacy — OIDC_ONLY=true with OIDC configured disables password_login and password_registration', () => {
    vi.stubEnv('OIDC_ONLY', 'true');
    vi.stubEnv('OIDC_ISSUER', 'https://sso.example.com');
    vi.stubEnv('OIDC_CLIENT_ID', 'trek-client');
    const t = resolveAuthToggles();
    expect(t.password_login).toBe(false);
    expect(t.password_registration).toBe(false);
    expect(t.oidc_login).toBe(true);
    expect(t.oidc_registration).toBe(true);
  });

  it('AUTH-DB-022c: legacy — allow_registration=false disables both password and oidc registration', () => {
    vi.stubEnv('OIDC_ONLY', '');
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'false')").run();
    const t = resolveAuthToggles();
    expect(t.password_login).toBe(true);
    expect(t.password_registration).toBe(false);
    expect(t.oidc_login).toBe(true);
    expect(t.oidc_registration).toBe(false);
  });

  it('AUTH-DB-022d: new granular keys take precedence over legacy keys', () => {
    vi.stubEnv('OIDC_ONLY', '');
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'false')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_registration', 'true')").run();
    const t = resolveAuthToggles();
    // New key present → use new keys, allow_registration ignored
    expect(t.password_registration).toBe(true);
    expect(t.oidc_registration).toBe(true); // defaults to true when key not set
  });

  it('AUTH-DB-022e: OIDC_ONLY env var overrides new granular keys for password toggles', () => {
    vi.stubEnv('OIDC_ONLY', 'true');
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_login', 'true')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_registration', 'true')").run();
    const t = resolveAuthToggles();
    // OIDC_ONLY forces password toggles off even when DB says true
    expect(t.password_login).toBe(false);
    expect(t.password_registration).toBe(false);
  });

  it('AUTH-DB-022f: individual granular keys can be set independently', () => {
    vi.stubEnv('OIDC_ONLY', '');
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_login', 'true')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_registration', 'false')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_login', 'true')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_registration', 'false')").run();
    const t = resolveAuthToggles();
    expect(t.password_login).toBe(true);
    expect(t.password_registration).toBe(false);
    expect(t.oidc_login).toBe(true);
    expect(t.oidc_registration).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// setupMfa
// ---------------------------------------------------------------------------

describe('setupMfa', () => {
  it('AUTH-DB-022: returns 403 in demo mode for demo@nomad.app', () => {
    vi.stubEnv('DEMO_MODE', 'true');
    const { user } = createUser(testDb, { email: 'demo@nomad.app' });
    const result = setupMfa(user.id, 'demo@nomad.app');
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/demo mode/i);
    vi.unstubAllEnvs();
  });

  it('AUTH-DB-023: returns 400 when MFA is already enabled', () => {
    const { user } = createUser(testDb);
    testDb.prepare('UPDATE users SET mfa_enabled = 1 WHERE id = ?').run(user.id);
    const result = setupMfa(user.id, user.email);
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/already enabled/i);
  });

  it('AUTH-DB-024: returns secret and otpauth_url when MFA setup starts successfully', () => {
    const { user } = createUser(testDb);
    const result = setupMfa(user.id, user.email);
    expect(result.error).toBeUndefined();
    expect(typeof result.secret).toBe('string');
    expect(result.secret!.length).toBeGreaterThan(0);
    expect(typeof result.otpauth_url).toBe('string');
    expect(result.otpauth_url).toMatch(/^otpauth:\/\/totp\//);
    expect(result.qrPromise).toBeInstanceOf(Promise);
  });
});

// ---------------------------------------------------------------------------
// enableMfa
// ---------------------------------------------------------------------------

describe('enableMfa', () => {
  it('AUTH-DB-025: returns 400 when no verification code is provided', () => {
    const { user } = createUser(testDb);
    const result = enableMfa(user.id, undefined);
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/code is required/i);
  });

  it('AUTH-DB-026: returns 400 when there is no pending MFA setup', () => {
    const { user } = createUser(testDb);
    // No setupMfa called first, so no pending entry exists
    const result = enableMfa(user.id, '123456');
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/no mfa setup in progress/i);
  });
});

// ---------------------------------------------------------------------------
// disableMfa
// ---------------------------------------------------------------------------

describe('disableMfa', () => {
  it('AUTH-DB-027: returns 403 in demo mode for demo@nomad.app', () => {
    vi.stubEnv('DEMO_MODE', 'true');
    const { user } = createUser(testDb, { email: 'demo@nomad.app' });
    const result = disableMfa(user.id, 'demo@nomad.app', {
      password: 'password123',
      code: '000000',
    });
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/demo mode/i);
    vi.unstubAllEnvs();
  });

  it('AUTH-DB-028: returns 400 when password or code is missing', () => {
    const { user } = createUser(testDb);

    const missingCode = disableMfa(user.id, user.email, { password: 'pass', code: undefined });
    expect(missingCode.status).toBe(400);
    expect(missingCode.error).toMatch(/password and authenticator code/i);

    const missingPassword = disableMfa(user.id, user.email, { password: undefined, code: '123456' });
    expect(missingPassword.status).toBe(400);
    expect(missingPassword.error).toMatch(/password and authenticator code/i);
  });

  it('AUTH-DB-029: returns 400 when MFA is not enabled on the account', () => {
    const { user } = createUser(testDb);
    // mfa_enabled defaults to 0 / not set
    const result = disableMfa(user.id, user.email, { password: 'password123', code: '000000' });
    expect(result.status).toBe(400);
    expect(result.error).toMatch(/not enabled/i);
  });
});

// ---------------------------------------------------------------------------
// validateInviteToken
// ---------------------------------------------------------------------------

describe('validateInviteToken', () => {
  it('AUTH-DB-030: returns 404 for unknown token', () => {
    const result = validateInviteToken('no-such-token');
    expect(result.status).toBe(404);
  });

  it('AUTH-DB-031: returns 410 when max_uses exceeded', () => {
    // createInviteToken with used_count already at max
    const invite = createInviteToken(testDb, { max_uses: 1 });
    // manually set used_count = 1 to simulate exhaustion
    testDb.prepare('UPDATE invite_tokens SET used_count = 1 WHERE id = ?').run(invite.id);
    const result = validateInviteToken(invite.token);
    expect(result.status).toBe(410);
  });

  it('AUTH-DB-032: returns 410 when expired', () => {
    const invite = createInviteToken(testDb, { expires_at: '2000-01-01T00:00:00.000Z' });
    const result = validateInviteToken(invite.token);
    expect(result.status).toBe(410);
  });
});

// ---------------------------------------------------------------------------
// registerUser — OIDC-only / registration-disabled
// ---------------------------------------------------------------------------

describe('registerUser — OIDC-only / registration-disabled', () => {
  it('AUTH-DB-033: returns 403 when oidc_only=true and not first user', () => {
    createUser(testDb); // ensure userCount > 0
    testDb.prepare("INSERT INTO app_settings (key, value) VALUES ('oidc_only', 'true')").run();
    testDb.prepare("INSERT INTO app_settings (key, value) VALUES ('oidc_issuer', 'https://x')").run();
    testDb.prepare("INSERT INTO app_settings (key, value) VALUES ('oidc_client_id', 'id')").run();

    const result = registerUser({ username: 'u', email: 'new@x.com', password: 'Secure123!' });
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/password registration is disabled/i);
  });

  it('AUTH-DB-034: returns 403 when registration is disabled and no invite', () => {
    createUser(testDb); // ensure userCount > 0
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'false')").run();

    const result = registerUser({ username: 'u2', email: 'n2@x.com', password: 'Secure123!' });
    expect(result.status).toBe(403);
  });
});

// ---------------------------------------------------------------------------
// loginUser — OIDC-only mode
// ---------------------------------------------------------------------------

describe('loginUser — OIDC-only mode', () => {
  it('AUTH-DB-035: returns 403 when oidc_only=true', () => {
    const { user, password } = createUser(testDb);
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_only', 'true')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_issuer', 'https://x')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_client_id', 'id')").run();

    const result = loginUser({ email: user.email, password });
    expect(result.status).toBe(403);
  });
});

// ---------------------------------------------------------------------------
// changePassword — OIDC-only mode
// ---------------------------------------------------------------------------

describe('changePassword — OIDC-only mode', () => {
  it('AUTH-DB-036: returns 403 when oidc_only=true', () => {
    const { user, password } = createUser(testDb);
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_only', 'true')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_issuer', 'https://x')").run();
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_client_id', 'id')").run();

    const result = changePassword(user.id, user.email, { current_password: password, new_password: 'New1234!' });
    expect(result.status).toBe(403);
  });
});

// ---------------------------------------------------------------------------
// disableMfa — require_mfa policy
// ---------------------------------------------------------------------------

describe('disableMfa — require_mfa policy', () => {
  it('AUTH-DB-037: returns 403 when require_mfa=true is set globally', () => {
    const { user } = createUser(testDb);
    testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('require_mfa', 'true')").run();

    const result = disableMfa(user.id, user.email, { password: 'pass', code: '123456' });
    expect(result.status).toBe(403);
    expect(result.error).toMatch(/cannot be disabled/i);
  });
});

// ---------------------------------------------------------------------------
// verifyMfaLogin — validation
// ---------------------------------------------------------------------------

describe('verifyMfaLogin — validation', () => {
  it('AUTH-DB-038: returns 400 when mfa_token or code is missing', () => {
    const result = verifyMfaLogin({ mfa_token: undefined, code: undefined });
    expect(result.status).toBe(400);
  });

  it('AUTH-DB-039: returns 401 when mfa_token has wrong purpose', () => {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const jwt = require('jsonwebtoken');
    const tok = jwt.sign({ id: 1, purpose: 'wrong' }, 'test-secret', { expiresIn: '5m', algorithm: 'HS256' });
    const result = verifyMfaLogin({ mfa_token: tok, code: '123456' });
    expect(result.status).toBe(401);
    expect(result.error).toMatch(/invalid/i);
  });

  it('AUTH-DB-040: returns 401 when user not found for valid mfa_token', () => {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const jwt = require('jsonwebtoken');
    const tok = jwt.sign({ id: 99999, purpose: 'mfa_login' }, 'test-secret', { expiresIn: '5m', algorithm: 'HS256' });
    const result = verifyMfaLogin({ mfa_token: tok, code: '123456' });
    expect(result.status).toBe(401);
  });
});

// ---------------------------------------------------------------------------
// MCP token service
// ---------------------------------------------------------------------------

describe('MCP token service', () => {
  it('AUTH-DB-041: createMcpToken returns 400 when name is missing', () => {
    const { user } = createUser(testDb);
    const result = createMcpToken(user.id, undefined);
    expect(result.status).toBe(400);
  });

  it('AUTH-DB-042: createMcpToken returns 400 when name exceeds 100 chars', () => {
    const { user } = createUser(testDb);
    const result = createMcpToken(user.id, 'a'.repeat(101));
    expect(result.status).toBe(400);
  });

  it('AUTH-DB-043: createMcpToken creates token and returns raw_token', () => {
    const { user } = createUser(testDb);
    const result = createMcpToken(user.id, 'My Token');
    expect(result.token).toBeDefined();
    expect((result.token as any).raw_token).toMatch(/^trek_/);
  });

  it('AUTH-DB-044: createMcpToken returns 400 when user has 10 tokens already', () => {
    const { user } = createUser(testDb);
    for (let i = 0; i < 10; i++) {
      testDb.prepare(
        'INSERT INTO mcp_tokens (user_id, name, token_hash, token_prefix) VALUES (?, ?, ?, ?)'
      ).run(user.id, `Token ${i}`, `hash${i}`, `trek_prefix${i}`);
    }
    const result = createMcpToken(user.id, 'One More');
    expect(result.status).toBe(400);
  });

  it('AUTH-DB-045: deleteMcpToken returns 404 for non-existent token', () => {
    const { user } = createUser(testDb);
    const result = deleteMcpToken(user.id, '99999');
    expect(result.status).toBe(404);
  });

  it('AUTH-DB-046: deleteMcpToken deletes the token and returns success', () => {
    const { user } = createUser(testDb);
    const created = createMcpToken(user.id, 'Deletable Token');
    const tokenId = String((created.token as any).id);

    const result = deleteMcpToken(user.id, tokenId);
    expect(result).toEqual({ success: true });

    const row = testDb.prepare('SELECT id FROM mcp_tokens WHERE id = ?').get(tokenId);
    expect(row).toBeUndefined();
  });
});
