/**
 * PostgreSQL UserRepository. Parameterized queries only.
 * Unique email is enforced by the DB (citext UNIQUE) — race-safe.
 */
import type pg from 'pg';
import type { UserRepository } from '../domain/ports.js';
import type { NewUser, User } from '../domain/user.js';
import type { Role } from '../../../shared/auth.js';
import { EmailAlreadyRegisteredError } from '../domain/errors.js';

interface UserRow {
  id: string;
  email: string;
  password_hash: string;
  role: Role;
  created_at: Date;
}

const UNIQUE_VIOLATION = '23505';

export class PgUserRepository implements UserRepository {
  constructor(private readonly pool: pg.Pool) {}

  async create(user: NewUser): Promise<User> {
    try {
      const result = await this.pool.query<UserRow>(
        `INSERT INTO identity_users (email, password_hash)
         VALUES ($1, $2)
         RETURNING id, email, role, created_at`,
        [user.email, user.passwordHash],
      );
      const row = result.rows[0];
      if (!row) {
        throw new Error('identity_users INSERT returned no row');
      }
      return { id: row.id, email: row.email, role: row.role, createdAt: row.created_at };
    } catch (error) {
      if (isPgError(error) && error.code === UNIQUE_VIOLATION) {
        throw new EmailAlreadyRegisteredError();
      }
      throw error;
    }
  }

  async findByEmail(email: string): Promise<(User & { passwordHash: string }) | undefined> {
    const result = await this.pool.query<UserRow>(
      `SELECT id, email, password_hash, role, created_at
       FROM identity_users
       WHERE email = $1`,
      [email],
    );
    const row = result.rows[0];
    if (!row) {
      return undefined;
    }
    return {
      id: row.id,
      email: row.email,
      role: row.role,
      createdAt: row.created_at,
      passwordHash: row.password_hash,
    };
  }

  async findById(id: string): Promise<User | undefined> {
    const result = await this.pool.query<UserRow>(
      `SELECT id, email, role, created_at
       FROM identity_users WHERE id = $1`,
      [id],
    );
    const row = result.rows[0];
    if (!row) return undefined;
    return { id: row.id, email: row.email, role: row.role, createdAt: row.created_at };
  }

  async listUsers(params?: {
    limit?: number;
    offset?: number;
    role?: string;
    q?: string;
  }): Promise<{ items: User[]; total: number }> {
    const limit = params?.limit ?? 20;
    const offset = params?.offset ?? 0;
    const conditions: string[] = [];
    const values: unknown[] = [];
    let i = 1;
    if (params?.role) {
      conditions.push(`role = $${i++}`);
      values.push(params.role);
    }
    if (params?.q) {
      conditions.push(`(email ILIKE $${i++} OR role ILIKE $${i++})`);
      values.push(`%${params.q}%`);
      values.push(`%${params.q}%`);
    }
    const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
    const countResult = await this.pool.query<{ count: string }>(
      `SELECT COUNT(*) FROM identity_users ${where}`,
      values,
    );
    const total = parseInt(countResult.rows[0]?.count ?? '0', 10);
    const rows = await this.pool.query<UserRow>(
      `SELECT id, email, role, created_at FROM identity_users ${where} ORDER BY created_at DESC LIMIT $${i++} OFFSET $${i}`,
      [...values, limit, offset],
    );
    return {
      items: rows.rows.map((r) => ({
        id: r.id,
        email: r.email,
        role: r.role,
        createdAt: r.created_at,
      })),
      total,
    };
  }

  async updateUser(id: string, patch: { role?: string; passwordHash?: string }): Promise<User> {
    const sets: string[] = [];
    const values: unknown[] = [];
    let i = 1;
    if (patch.role) {
      sets.push(`role = $${i++}`);
      values.push(patch.role);
    }
    if (patch.passwordHash) {
      sets.push(`password_hash = $${i++}`);
      values.push(patch.passwordHash);
    }
    if (!sets.length) {
      const existing = await this.findById(id);
      if (!existing) throw new Error('User not found');
      return existing;
    }
    values.push(id);
    const result = await this.pool.query<UserRow>(
      `UPDATE identity_users SET ${sets.join(', ')} WHERE id = $${i} RETURNING id, email, role, created_at`,
      values,
    );
    const row = result.rows[0];
    if (!row) throw new Error('User not found');
    return { id: row.id, email: row.email, role: row.role, createdAt: row.created_at };
  }

  async deleteUser(id: string): Promise<void> {
    await this.pool.query(`DELETE FROM identity_users WHERE id = $1`, [id]);
  }
}

function isPgError(error: unknown): error is { code: string } {
  return typeof error === 'object' && error !== null && 'code' in error;
}
