import type pg from 'pg';
import { BrandSlugAlreadyExistsError } from '../domain/errors.js';
import type { Brand, BrandPatch, NewBrand } from '../domain/brand.js';
import type { BrandRepository } from '../domain/ports.js';

interface BrandRow {
  id: string;
  name: string;
  slug: string;
  seo_title: string | null;
  seo_description: string | null;
  created_at: Date;
  updated_at: Date;
}

const UNIQUE_VIOLATION = '23505';

const UPDATABLE: ReadonlyArray<[keyof BrandPatch, string]> = [
  ['name', 'name'],
  ['slug', 'slug'],
  ['seoTitle', 'seo_title'],
  ['seoDescription', 'seo_description'],
];

export class PgBrandRepository implements BrandRepository {
  constructor(private readonly pool: pg.Pool) {}

  async findById(id: string): Promise<Brand | undefined> {
    const result = await this.pool.query<BrandRow>('SELECT * FROM brands_brands WHERE id = $1', [
      id,
    ]);
    const row = result.rows[0];
    return row ? toBrand(row) : undefined;
  }

  async findBySlug(slug: string): Promise<Brand | undefined> {
    const result = await this.pool.query<BrandRow>('SELECT * FROM brands_brands WHERE slug = $1', [
      slug,
    ]);
    const row = result.rows[0];
    return row ? toBrand(row) : undefined;
  }

  async list(): Promise<Brand[]> {
    const result = await this.pool.query<BrandRow>(
      'SELECT * FROM brands_brands ORDER BY name ASC, id ASC',
    );
    return result.rows.map(toBrand);
  }

  async create(input: NewBrand): Promise<Brand> {
    try {
      const result = await this.pool.query<BrandRow>(
        `INSERT INTO brands_brands (name, slug, seo_title, seo_description)
         VALUES ($1, $2, $3, $4)
         RETURNING *`,
        [input.name, input.slug, input.seoTitle ?? null, input.seoDescription ?? null],
      );
      const row = result.rows[0];
      if (!row) {
        throw new Error('brands_brands INSERT returned no row');
      }
      return toBrand(row);
    } catch (error) {
      if (isPgError(error, UNIQUE_VIOLATION)) {
        throw new BrandSlugAlreadyExistsError();
      }
      throw error;
    }
  }

  async update(id: string, patch: BrandPatch): Promise<Brand | 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.findById(id);
    }

    values.push(id);
    try {
      const result = await this.pool.query<BrandRow>(
        `UPDATE brands_brands SET ${setClauses.join(', ')}, updated_at = now()
         WHERE id = $${values.length}
         RETURNING *`,
        values,
      );
      const row = result.rows[0];
      return row ? toBrand(row) : undefined;
    } catch (error) {
      if (isPgError(error, UNIQUE_VIOLATION)) {
        throw new BrandSlugAlreadyExistsError();
      }
      throw error;
    }
  }

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

function toBrand(row: BrandRow): Brand {
  return {
    id: row.id,
    name: row.name,
    slug: row.slug,
    seoTitle: row.seo_title,
    seoDescription: row.seo_description,
    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;
}
