/**
 * PostgreSQL ProfileRepository. Parameterized queries only; update columns are
 * whitelisted, never built from user input.
 */
import type pg from 'pg';
import type { ProfileRepository } from '../domain/ports.js';
import type {
  CustomerListOptions,
  CustomerListResult,
  CustomerSummary,
  Profile,
  ProfilePatch,
  UserPreferences,
} from '../domain/profile.js';
import { DEFAULT_PREFERENCES } from '../domain/profile.js';

interface ProfileRow {
  user_id: string;
  display_name: string | null;
  phone: string | null;
  preferences: Partial<UserPreferences> | null;
  created_at: Date;
  updated_at: Date;
}

interface CustomerRow {
  user_id: string;
  email: string;
  role: string;
  display_name: string | null;
  phone: string | null;
  created_at: Date;
}

export class PgProfileRepository implements ProfileRepository {
  constructor(private readonly pool: pg.Pool) {}

  async findByUserId(userId: string): Promise<Profile | undefined> {
    const result = await this.pool.query<ProfileRow>(
      `SELECT user_id, display_name, phone, preferences, created_at, updated_at
       FROM users_profiles WHERE user_id = $1`,
      [userId],
    );
    const row = result.rows[0];
    return row ? toProfile(row) : undefined;
  }

  async upsert(userId: string, patch: ProfilePatch): Promise<Profile> {
    await this.pool.query(
      `INSERT INTO users_profiles (user_id) VALUES ($1)
       ON CONFLICT (user_id) DO NOTHING`,
      [userId],
    );

    const setClauses: string[] = [];
    const values: unknown[] = [];
    if (patch.displayName !== undefined) {
      values.push(patch.displayName);
      setClauses.push(`display_name = $${values.length}`);
    }
    if (patch.phone !== undefined) {
      values.push(patch.phone);
      setClauses.push(`phone = $${values.length}`);
    }
    if (patch.preferences !== undefined) {
      // Merge parcial: solo cambian las claves enviadas.
      values.push(JSON.stringify(patch.preferences));
      setClauses.push(`preferences = COALESCE(preferences, '{}'::jsonb) || $${values.length}::jsonb`);
    }
    if (setClauses.length > 0) {
      values.push(userId);
      await this.pool.query(
        `UPDATE users_profiles SET ${setClauses.join(', ')}, updated_at = now()
         WHERE user_id = $${values.length}`,
        values,
      );
    }

    const profile = await this.findByUserId(userId);
    if (!profile) {
      throw new Error('users_profiles upsert did not return a row');
    }
    return profile;
  }

  async list(): Promise<Profile[]> {
    const result = await this.pool.query<ProfileRow>(
      `SELECT user_id, display_name, phone, preferences, created_at, updated_at
       FROM users_profiles ORDER BY created_at`,
    );
    return result.rows.map(toProfile);
  }

  async listCustomers(opts: CustomerListOptions): Promise<CustomerListResult> {
    const searchFilter = opts.q ? `%${opts.q}%` : null;

    // F-154: Customers endpoint serves storefront customers only (role 'customer').
    // role is a code literal (DB column compared to a constant), not user input,
    // so it does not affect the parameterized $N index of the email search.
    const countResult = await this.pool.query<{ cnt: string }>(
      `SELECT COUNT(*)::text AS cnt
       FROM identity_users iu
       WHERE iu.role = 'customer' AND ($1::text IS NULL OR iu.email ILIKE $1)`,
      [searchFilter],
    );
    const total = parseInt(countResult.rows[0]?.cnt ?? '0', 10);

    const result = await this.pool.query<CustomerRow>(
      `SELECT iu.id AS user_id, iu.email, iu.role,
              up.display_name, up.phone, iu.created_at
       FROM identity_users iu
       LEFT JOIN users_profiles up ON up.user_id = iu.id
       WHERE iu.role = 'customer' AND ($1::text IS NULL OR iu.email ILIKE $1)
       ORDER BY iu.created_at DESC
       LIMIT $2 OFFSET $3`,
      [searchFilter, opts.limit, opts.offset],
    );

    return {
      items: result.rows.map(toCustomerSummary),
      total,
    };
  }

  async findCustomerById(userId: string): Promise<CustomerSummary | undefined> {
    const result = await this.pool.query<CustomerRow>(
      `SELECT iu.id AS user_id, iu.email, iu.role,
              up.display_name, up.phone, iu.created_at
       FROM identity_users iu
       LEFT JOIN users_profiles up ON up.user_id = iu.id
       WHERE iu.id = $1`,
      [userId],
    );
    const row = result.rows[0];
    return row ? toCustomerSummary(row) : undefined;
  }
}

function toProfile(row: ProfileRow): Profile {
  return {
    userId: row.user_id,
    displayName: row.display_name,
    phone: row.phone,
    preferences: { ...DEFAULT_PREFERENCES, ...(row.preferences ?? {}) },
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

function toCustomerSummary(row: CustomerRow): CustomerSummary {
  return {
    userId: row.user_id,
    email: row.email,
    role: row.role,
    displayName: row.display_name,
    phone: row.phone,
    createdAt: row.created_at,
  };
}
