import type pg from 'pg';
import {
  ProductVariantCodeAlreadyExistsError,
  type ProductVariantCode,
} from '../domain/errors.js';
import type { ProductVariantRepository } from '../domain/ports.js';
import type { NewProductVariant, ProductVariant, ProductVariantPatch } from '../domain/variant.js';

interface VariantRow {
  id: string;
  product_id: string;
  sku: string;
  ean: string | null;
  attributes: Record<string, unknown>;
  created_at: Date;
  updated_at: Date;
}

const UNIQUE_VIOLATION = '23505';

const UPDATABLE: ReadonlyArray<[keyof ProductVariantPatch, string]> = [
  ['sku', 'sku'],
  ['ean', 'ean'],
  ['attributes', 'attributes'],
];

export class PgProductVariantRepository implements ProductVariantRepository {
  constructor(private readonly pool: pg.Pool) {}

  async listByProductId(productId: string): Promise<ProductVariant[]> {
    const result = await this.pool.query<VariantRow>(
      'SELECT * FROM catalog_product_variants WHERE product_id = $1 ORDER BY created_at, sku',
      [productId],
    );
    return result.rows.map(toVariant);
  }

  async create(productId: string, input: NewProductVariant): Promise<ProductVariant> {
    try {
      const result = await this.pool.query<VariantRow>(
        `INSERT INTO catalog_product_variants (product_id, sku, ean, attributes)
         VALUES ($1, $2, $3, $4)
         RETURNING *`,
        [productId, input.sku, input.ean ?? null, input.attributes ?? {}],
      );
      const row = result.rows[0];
      if (!row) {
        throw new Error('catalog_product_variants INSERT returned no row');
      }
      return toVariant(row);
    } catch (error) {
      if (isPgError(error, UNIQUE_VIOLATION)) {
        throw new ProductVariantCodeAlreadyExistsError(uniqueConstraintField(error));
      }
      throw error;
    }
  }

  async update(
    productId: string,
    variantId: string,
    patch: ProductVariantPatch,
  ): Promise<ProductVariant | undefined> {
    const setClauses: string[] = [];
    const values: unknown[] = [];
    for (const [key, column] of UPDATABLE) {
      if (key in patch) {
        values.push(patch[key]);
        setClauses.push(`${column} = $${values.length}`);
      }
    }
    if (setClauses.length === 0) {
      return this.findScoped(productId, variantId);
    }

    values.push(productId, variantId);
    try {
      const result = await this.pool.query<VariantRow>(
        `UPDATE catalog_product_variants SET ${setClauses.join(', ')}, updated_at = now()
         WHERE product_id = $${values.length - 1} AND id = $${values.length}
         RETURNING *`,
        values,
      );
      const row = result.rows[0];
      return row ? toVariant(row) : undefined;
    } catch (error) {
      if (isPgError(error, UNIQUE_VIOLATION)) {
        throw new ProductVariantCodeAlreadyExistsError(uniqueConstraintField(error));
      }
      throw error;
    }
  }

  private async findScoped(
    productId: string,
    variantId: string,
  ): Promise<ProductVariant | undefined> {
    const result = await this.pool.query<VariantRow>(
      'SELECT * FROM catalog_product_variants WHERE product_id = $1 AND id = $2',
      [productId, variantId],
    );
    const row = result.rows[0];
    return row ? toVariant(row) : undefined;
  }
}

function toVariant(row: VariantRow): ProductVariant {
  return {
    id: row.id,
    productId: row.product_id,
    sku: row.sku,
    ean: row.ean,
    attributes: row.attributes,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

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

function uniqueConstraintField(error: unknown): ProductVariantCode | undefined {
  if (typeof error !== 'object' || error === null || !('constraint' in error)) {
    return undefined;
  }
  const constraint = error.constraint;
  if (typeof constraint !== 'string') return undefined;
  if (constraint.endsWith('_sku_key')) return 'sku';
  if (constraint.endsWith('_ean_key')) return 'ean';
  return undefined;
}
