import type pg from 'pg';
import type {
  BackofficeRole,
  BackofficeUser,
  BackofficeUserWithHash,
  NewBackofficeUser,
} from '../domain/backoffice-user.js';
import type { BackofficeUserRepository } from '../domain/ports.js';
import { BackofficeEmailAlreadyRegisteredError } from '../domain/errors.js';

interface UserRow {
  id: string;
  email: string;
  password_hash: string;
  role: string;
  mfa_enrolled: boolean;
  created_at: Date;
  updated_at: Date;
}

const UNIQUE_VIOLATION = '23505';

export class PgBackofficeUserRepository implements BackofficeUserRepository {
  constructor(private readonly pool: pg.Pool) {}

  async create(input: NewBackofficeUser): Promise<BackofficeUser> {
    try {
      const result = await this.pool.query<UserRow>(
        `INSERT INTO backoffice_users (email, password_hash, role, mfa_enrolled)
         VALUES ($1, $2, COALESCE($3, 'admin'), COALESCE($4, false))
         RETURNING *`,
        [
          input.email.toLowerCase(),
          input.passwordHash,
          input.role ?? null,
          input.mfaEnrolled ?? null,
        ],
      );
      const row = result.rows[0];
      if (!row) throw new Error('backoffice_users INSERT returned no row');
      return toUser(row);
    } catch (error) {
      if (isUniqueViolation(error)) throw new BackofficeEmailAlreadyRegisteredError();
      throw error;
    }
  }

  async findByEmail(email: string): Promise<BackofficeUserWithHash | undefined> {
    const result = await this.pool.query<UserRow>(
      'SELECT * FROM backoffice_users WHERE email = $1',
      [email.toLowerCase()],
    );
    const row = result.rows[0];
    return row ? { ...toUser(row), passwordHash: row.password_hash } : undefined;
  }

  async findById(id: string): Promise<BackofficeUser | undefined> {
    const result = await this.pool.query<UserRow>('SELECT * FROM backoffice_users WHERE id = $1', [
      id,
    ]);
    const row = result.rows[0];
    return row ? toUser(row) : undefined;
  }

  async updateMfaEnrolled(id: string, enrolled: boolean): Promise<void> {
    await this.pool.query(
      'UPDATE backoffice_users SET mfa_enrolled = $2, updated_at = now() WHERE id = $1',
      [id, enrolled],
    );
  }
}

function toUser(row: UserRow): BackofficeUser {
  return {
    id: row.id,
    email: row.email,
    role: row.role as BackofficeRole,
    mfaEnrolled: row.mfa_enrolled,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

function isUniqueViolation(error: unknown): boolean {
  return (
    typeof error === 'object' &&
    error !== null &&
    'code' in error &&
    (error as { code: string }).code === UNIQUE_VIOLATION
  );
}
