import type pg from 'pg';
import type { Cart, CartItem } from '../domain/cart.js';
import type { CartRepository } from '../domain/ports.js';
import type { CartItemInput } from '../domain/cart.js';

interface CartRow {
  id: string;
  user_id: string;
  promo_code: string | null;
  created_at: Date;
  updated_at: Date;
}
interface ItemRow {
  id: string;
  cart_id: string;
  product_id: string;
  variant_id: string;
  quantity: number;
  created_at: Date;
  updated_at: Date;
}

export class PgCartRepository implements CartRepository {
  constructor(private readonly pool: pg.Pool) {}

  async getOrCreate(userId: string): Promise<Cart> {
    const cart = await this.ensureCart(userId);
    return this.loadCart(cart);
  }

  async addItem(userId: string, input: CartItemInput): Promise<Cart> {
    const cart = await this.ensureCart(userId);
    await this.pool.query(
      `INSERT INTO cart_items (cart_id, product_id, variant_id, quantity)
       VALUES ($1, $2, $3, $4)
       ON CONFLICT (cart_id, variant_id) DO UPDATE
         SET quantity = cart_items.quantity + EXCLUDED.quantity, updated_at = now()`,
      [cart.id, input.productId, input.variantId, input.quantity],
    );
    return this.loadCart(cart);
  }

  async changeQuantity(userId: string, variantId: string, quantity: number): Promise<Cart> {
    const cart = await this.ensureCart(userId);
    await this.pool.query(
      `UPDATE cart_items SET quantity = $3, updated_at = now()
       WHERE cart_id = $1 AND variant_id = $2`,
      [cart.id, variantId, quantity],
    );
    return this.loadCart(cart);
  }

  async removeItem(userId: string, variantId: string): Promise<Cart> {
    const cart = await this.ensureCart(userId);
    await this.pool.query('DELETE FROM cart_items WHERE cart_id = $1 AND variant_id = $2', [
      cart.id,
      variantId,
    ]);
    return this.loadCart(cart);
  }

  async setPromoCode(userId: string, code: string | null): Promise<Cart> {
    const cart = await this.ensureCart(userId);
    const result = await this.pool.query<CartRow>(
      'UPDATE cart_carts SET promo_code = $2, updated_at = now() WHERE id = $1 RETURNING *',
      [cart.id, code],
    );
    const row = result.rows[0];
    if (!row) throw new Error('cart_carts promo update returned no row');
    return this.loadCart(row);
  }

  private async ensureCart(userId: string): Promise<CartRow> {
    const result = await this.pool.query<CartRow>(
      `INSERT INTO cart_carts (user_id) VALUES ($1)
       ON CONFLICT (user_id) DO UPDATE SET updated_at = cart_carts.updated_at
       RETURNING *`,
      [userId],
    );
    const row = result.rows[0];
    if (!row) throw new Error('cart_carts upsert returned no row');
    return row;
  }

  private async loadCart(row: CartRow): Promise<Cart> {
    const result = await this.pool.query<ItemRow>(
      'SELECT * FROM cart_items WHERE cart_id = $1 ORDER BY created_at, id',
      [row.id],
    );
    return {
      id: row.id,
      userId: row.user_id,
      promoCode: row.promo_code,
      items: result.rows.map(toItem),
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }
}

function toItem(row: ItemRow): CartItem {
  return {
    id: row.id,
    cartId: row.cart_id,
    productId: row.product_id,
    variantId: row.variant_id,
    quantity: row.quantity,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}
