import type pg from 'pg';
import type { NewPromotion, Promotion, PromotionType } from '../domain/promotion.js';
import type { PromotionRepository } from '../domain/ports.js';

interface PromotionRow {
  code: string;
  type: PromotionType;
  value: number;
  starts_at: Date;
  ends_at: Date;
  usage_limit: number | null;
  usage_count: number;
  active: boolean;
  created_at: Date;
  updated_at: Date;
}

export class PgPromotionRepository implements PromotionRepository {
  constructor(private readonly pool: pg.Pool) {}

  async findByCode(code: string): Promise<Promotion | undefined> {
    const result = await this.pool.query<PromotionRow>(
      'SELECT * FROM promotions_promotions WHERE code = $1',
      [code],
    );
    const row = result.rows[0];
    return row ? toPromotion(row) : undefined;
  }

  async create(input: NewPromotion): Promise<Promotion> {
    const result = await this.pool.query<PromotionRow>(
      `INSERT INTO promotions_promotions (code, type, value, starts_at, ends_at, usage_limit, active)
       VALUES ($1, $2, $3, $4, $5, $6, $7)
       ON CONFLICT (code) DO UPDATE
         SET type = EXCLUDED.type, value = EXCLUDED.value, starts_at = EXCLUDED.starts_at,
             ends_at = EXCLUDED.ends_at, usage_limit = EXCLUDED.usage_limit,
             active = EXCLUDED.active, updated_at = now()
       RETURNING *`,
      [
        input.code,
        input.type,
        input.value,
        input.startsAt,
        input.endsAt,
        input.usageLimit ?? null,
        input.active ?? true,
      ],
    );
    const row = result.rows[0];
    if (!row) throw new Error('promotions_promotions upsert returned no row');
    return toPromotion(row);
  }

  async findAll(): Promise<Promotion[]> {
    const result = await this.pool.query<PromotionRow>(
      'SELECT * FROM promotions_promotions ORDER BY created_at DESC',
    );
    return result.rows.map(toPromotion);
  }

  async update(code: string, patch: Partial<NewPromotion>): Promise<Promotion> {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;

    if (patch.code !== undefined) {
      fields.push(`code = $${idx++}`);
      values.push(patch.code);
    }
    if (patch.type !== undefined) {
      fields.push(`type = $${idx++}`);
      values.push(patch.type);
    }
    if (patch.value !== undefined) {
      fields.push(`value = $${idx++}`);
      values.push(patch.value);
    }
    if (patch.startsAt !== undefined) {
      fields.push(`starts_at = $${idx++}`);
      values.push(patch.startsAt);
    }
    if (patch.endsAt !== undefined) {
      fields.push(`ends_at = $${idx++}`);
      values.push(patch.endsAt);
    }
    if (patch.usageLimit !== undefined) {
      fields.push(`usage_limit = $${idx++}`);
      values.push(patch.usageLimit);
    }
    if (patch.active !== undefined) {
      fields.push(`active = $${idx++}`);
      values.push(patch.active);
    }

    fields.push('updated_at = now()');
    values.push(code);

    const result = await this.pool.query<PromotionRow>(
      `UPDATE promotions_promotions SET ${fields.join(', ')} WHERE code = $${idx} RETURNING *`,
      values,
    );
    const row = result.rows[0];
    if (!row) throw new Error('Promotion not found');
    return toPromotion(row);
  }

  async delete(code: string): Promise<void> {
    await this.pool.query('DELETE FROM promotions_promotions WHERE code = $1', [code]);
  }
}

function toPromotion(row: PromotionRow): Promotion {
  return {
    code: row.code,
    type: row.type,
    value: row.value,
    startsAt: row.starts_at,
    endsAt: row.ends_at,
    usageLimit: row.usage_limit,
    usageCount: row.usage_count,
    active: row.active,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}
