import type pg from 'pg';
import type { OrderRepository } from '../domain/ports.js';
import type { Order, OrderItem, OrderItemInput, OrderState, OrderView } from '../domain/order.js';

interface OrderRow {
  id: string;
  user_id: string;
  email: string | null;
  idempotency_key: string | null;
  state: OrderState;
  currency: 'EUR';
  subtotal_cents: number;
  discount_cents: number;
  tax_cents: number;
  total_cents: number;
  tracking_number: string | null;
  courier: string | null;
  customer_name: string | null;
  customer_phone: string | null;
  phone: string | null;
  created_at: Date;
  updated_at: Date;
}

interface AddressRow {
  recipient_name: string;
  street: string;
  city: string;
  postal_code: string;
  country: string;
}

interface PaymentRow {
  provider: string;
  status: string;
  amount_cents: number;
  provider_payment_id: string | null;
  created_at: Date;
}
interface ItemRow {
  id: string;
  order_id: string;
  product_id: string;
  variant_id: string;
  sku: string;
  ean: string | null;
  name: string;
  unit_price_cents: number;
  discount_cents: number;
  tax_cents: number;
  quantity: number;
  created_at: Date;
}

interface CreateInput {
  userId: string;
  idempotencyKey: string | null;
  items: OrderItemInput[];
  totals: { subtotalCents: number; discountCents: number; taxCents: number; totalCents: number };
}

export class PgOrderRepository implements OrderRepository {
  constructor(private readonly pool: pg.Pool) {}

  async create(input: CreateInput): Promise<OrderView> {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      const orderResult = await client.query<OrderRow>(
        `INSERT INTO orders_orders (user_id, idempotency_key, subtotal_cents, discount_cents, tax_cents, total_cents)
         VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
        [
          input.userId,
          input.idempotencyKey,
          input.totals.subtotalCents,
          input.totals.discountCents,
          input.totals.taxCents,
          input.totals.totalCents,
        ],
      );
      const order = orderResult.rows[0];
      if (!order) throw new Error('orders_orders INSERT returned no row');
      for (const item of input.items) {
        await client.query<ItemRow>(
          `INSERT INTO orders_items (order_id, product_id, variant_id, sku, ean, name, unit_price_cents, discount_cents, tax_cents, quantity)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`,
          [
            order.id,
            item.productId,
            item.variantId,
            item.sku,
            item.ean,
            item.name,
            item.unitPriceCents,
            item.discountCents,
            item.taxCents,
            item.quantity,
          ],
        );
      }
      await client.query('COMMIT');
      const loaded = await this.findById(order.id);
      if (!loaded) throw new Error('orders_orders load after create returned no row');
      return loaded;
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async findAll(): Promise<OrderView[]> {
    const result = await this.pool.query<OrderRow>(
      'SELECT o.*, u.email FROM orders_orders o LEFT JOIN identity_users u ON u.id = o.user_id ORDER BY o.created_at DESC',
    );
    return Promise.all(
      result.rows.map(async (order) => {
        const items = await this.pool.query<ItemRow>(
          'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY id',
          [order.id],
        );
        return toOrderView(order, items.rows.map(toItem));
      }),
    );
  }

  async search(filters: {
    state?: string;
    q?: string;
    limit: number;
    offset: number;
  }): Promise<{ items: OrderView[]; total: number }> {
    const where: string[] = [];
    const values: unknown[] = [];
    if (filters.state) {
      values.push(filters.state);
      where.push(`o.state = $${values.length}`);
    }
    if (filters.q?.trim()) {
      values.push(`%${filters.q.trim()}%`);
      where.push(`(o.id::text ILIKE $${values.length} OR u.email ILIKE $${values.length})`);
    }
    const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : '';
    const countResult = await this.pool.query<{ count: string }>(
      `SELECT COUNT(*)::text AS count
       FROM orders_orders o
       LEFT JOIN identity_users u ON u.id = o.user_id
       ${whereSql}`,
      values,
    );
    const total = Number(countResult.rows[0]?.count ?? '0');
    values.push(filters.limit, filters.offset);
    const result = await this.pool.query<OrderRow>(
      `SELECT o.*, u.email FROM orders_orders o
       LEFT JOIN identity_users u ON u.id = o.user_id
       ${whereSql}
       ORDER BY o.created_at DESC
       LIMIT $${values.length - 1} OFFSET $${values.length}`,
      values,
    );
    const items = await Promise.all(
      result.rows.map(async (order) => {
        const itemResult = await this.pool.query<ItemRow>(
          'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY id',
          [order.id],
        );
        return toOrderView(order, itemResult.rows.map(toItem));
      }),
    );
    return { items, total };
  }

  async findById(id: string): Promise<OrderView | undefined> {
    const orderResult = await this.pool.query<OrderRow>(
      `SELECT o.*, u.email,
              COALESCE(o.customer_name, p.display_name) AS customer_name,
              COALESCE(o.customer_phone, p.phone, u.phone) AS customer_phone,
              COALESCE(u.phone, p.phone) AS phone
       FROM orders_orders o
       LEFT JOIN identity_users u ON u.id = o.user_id
       LEFT JOIN users_profiles p ON p.user_id = o.user_id
       WHERE o.id = $1`,
      [id],
    );
    const order = orderResult.rows[0];
    if (!order) return undefined;
    const items = await this.pool.query<ItemRow>(
      'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY created_at, id',
      [id],
    );
    const [shippingRow] = await this.pool.query<AddressRow>(
      `SELECT recipient_name, street, city, postal_code, country
       FROM users_addresses WHERE user_id = $1 AND type = 'shipping' LIMIT 1`,
      [order.user_id],
    ).then((r) => r.rows);
    const [billingRow] = await this.pool.query<AddressRow>(
      `SELECT recipient_name, street, city, postal_code, country
       FROM users_addresses WHERE user_id = $1 AND type = 'billing' LIMIT 1`,
      [order.user_id],
    ).then((r) => r.rows);
    const [paymentRow] = await this.pool.query<PaymentRow>(
      `SELECT provider, status, amount_cents, provider_payment_id, created_at
       FROM payments_transactions
       WHERE order_id = $1 AND status = 'succeeded'
       ORDER BY created_at DESC LIMIT 1`,
      [id],
    ).then((r) => r.rows);
    return toOrderView(order, items.rows.map(toItem), shippingRow, billingRow, paymentRow);
  }

  async findByIdAndUserId(id: string, userId: string): Promise<OrderView | undefined> {
    const orderResult = await this.pool.query<OrderRow>(
      `SELECT o.*, u.email,
              COALESCE(o.customer_name, p.display_name) AS customer_name,
              COALESCE(o.customer_phone, p.phone, u.phone) AS customer_phone,
              COALESCE(u.phone, p.phone) AS phone
       FROM orders_orders o
       LEFT JOIN identity_users u ON u.id = o.user_id
       LEFT JOIN users_profiles p ON p.user_id = o.user_id
       WHERE o.id = $1 AND o.user_id = $2`,
      [id, userId],
    );
    const order = orderResult.rows[0];
    if (!order) return undefined;
    const items = await this.pool.query<ItemRow>(
      'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY created_at, id',
      [id],
    );
    return toOrderView(order, items.rows.map(toItem));
  }

  async updateState(id: string, state: OrderState, trackingNumber?: string, courier?: string): Promise<OrderView | undefined> {
    const result = await this.pool.query<OrderRow>(
      `UPDATE orders_orders SET state = $2, tracking_number = COALESCE($3, tracking_number), courier = COALESCE($4, courier), updated_at = now()
       WHERE id = $1 RETURNING *`,
      [id, state, trackingNumber ?? null, courier ?? null],
    );
    const order = result.rows[0];
    if (!order) return undefined;
    return this.findById(order.id);
  }

  async replaceItems(
    id: string,
    items: OrderItemInput[],
    totals: { subtotalCents: number; discountCents: number; taxCents: number; totalCents: number },
  ): Promise<OrderView | undefined> {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      const orderResult = await client.query<OrderRow>(
        `UPDATE orders_orders
         SET subtotal_cents = $2, discount_cents = $3, tax_cents = $4, total_cents = $5, updated_at = now()
         WHERE id = $1 RETURNING *`,
        [id, totals.subtotalCents, totals.discountCents, totals.taxCents, totals.totalCents],
      );
      if (!orderResult.rows[0]) {
        await client.query('ROLLBACK');
        return undefined;
      }
      await client.query('DELETE FROM orders_items WHERE order_id = $1', [id]);
      for (const item of items) {
        await client.query<ItemRow>(
          `INSERT INTO orders_items (order_id, product_id, variant_id, sku, ean, name, unit_price_cents, discount_cents, tax_cents, quantity)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
          [
            id,
            item.productId,
            item.variantId,
            item.sku,
            item.ean,
            item.name,
            item.unitPriceCents,
            item.discountCents,
            item.taxCents,
            item.quantity,
          ],
        );
      }
      await client.query('COMMIT');
      return this.findById(id);
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }
}

function toOrder(row: OrderRow): Order {
  return {
    id: row.id,
    userId: row.user_id,
    idempotencyKey: row.idempotency_key,
    state: row.state,
    currency: row.currency,
    subtotalCents: row.subtotal_cents,
    discountCents: row.discount_cents,
    taxCents: row.tax_cents,
    totalCents: row.total_cents,
    trackingNumber: row.tracking_number,
    courier: row.courier,
    customerName: row.customer_name ?? null,
    customerPhone: row.customer_phone ?? null,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

function toOrderView(
  row: OrderRow,
  items: OrderItem[],
  shipping?: AddressRow | null,
  billing?: AddressRow | null,
  payment?: PaymentRow | null,
): OrderView {
  const last4 = payment?.provider_payment_id
    ? payment.provider_payment_id.replace(/[^0-9]/g, '').slice(-4)
    : null;
  return {
    ...toOrder(row),
    email: row.email,
    phone: row.phone ?? null,
    items,
    shippingAddress: shipping ? { recipientName: shipping.recipient_name, street: shipping.street, city: shipping.city, postalCode: shipping.postal_code, country: shipping.country } : null,
    billingAddress: billing ? { recipientName: billing.recipient_name, street: billing.street, city: billing.city, postalCode: billing.postal_code, country: billing.country } : null,
    payment: payment ? { provider: payment.provider, status: payment.status, amountCents: payment.amount_cents, last4, createdAt: payment.created_at } : null,
  };
}

function toItem(row: ItemRow): OrderItem {
  return {
    id: row.id,
    orderId: row.order_id,
    productId: row.product_id,
    variantId: row.variant_id,
    sku: row.sku,
    ean: row.ean,
    name: row.name,
    unitPriceCents: row.unit_price_cents,
    discountCents: row.discount_cents,
    taxCents: row.tax_cents,
    quantity: row.quantity,
    createdAt: row.created_at,
  };
}
