import type { QueryResultRow } from 'pg';
import type { PostgreSQLClient } from './client';
import type { JoinCondition, OrderByCondition, QueryResult, WhereCondition } from './types';

/**
 * PostgreSQL Query Builder
 *
 * Provides a fluent interface for building SQL queries
 */
export class PostgreSQLQueryBuilder {
  private queryType: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | null = null;
  private selectColumns: string[] = [];
  private fromTable: string = '';
  private joins: JoinCondition[] = [];
  private whereConditions: WhereCondition[] = [];
  private groupByColumns: string[] = [];
  private havingConditions: WhereCondition[] = [];
  private orderByConditions: OrderByCondition[] = [];
  private limitCount: number | null = null;
  private offsetCount: number | null = null;
  private insertValues: Record<string, any> = {};
  private updateValues: Record<string, any> = {};

  private readonly client: PostgreSQLClient;

  constructor(client: PostgreSQLClient) {
    this.client = client;
  }

  /**
   * SELECT statement
   */
  select(columns: string | string[] = '*'): this {
    this.queryType = 'SELECT';
    this.selectColumns = Array.isArray(columns) ? columns : [columns];
    return this;
  }

  /**
   * FROM clause
   */
  from(table: string): this {
    this.fromTable = table;
    return this;
  }

  /**
   * JOIN clause
   */
  join(table: string, on: string, type: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL' = 'INNER'): this {
    this.joins.push({ type, table, on });
    return this;
  }

  /**
   * WHERE clause
   */
  where(column: string, operator: string, value?: any): this {
    this.whereConditions.push({ column, operator: operator as any, value });
    return this;
  }

  /**
   * GROUP BY clause
   */
  groupBy(columns: string | string[]): this {
    this.groupByColumns = Array.isArray(columns) ? columns : [columns];
    return this;
  }

  /**
   * ORDER BY clause
   */
  orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
    this.orderByConditions.push({ column, direction });
    return this;
  }

  /**
   * LIMIT clause
   */
  limit(count: number): this {
    this.limitCount = count;
    return this;
  }

  /**
   * OFFSET clause
   */
  offset(count: number): this {
    this.offsetCount = count;
    return this;
  }

  /**
   * INSERT statement
   */
  insert(table: string): this {
    this.queryType = 'INSERT';
    this.fromTable = table;
    return this;
  }

  /**
   * VALUES for INSERT
   */
  values(data: Record<string, any>): this {
    this.insertValues = data;
    return this;
  }

  /**
   * UPDATE statement
   */
  update(table: string): this {
    this.queryType = 'UPDATE';
    this.fromTable = table;
    return this;
  }

  /**
   * SET for UPDATE
   */
  set(data: Record<string, any>): this {
    this.updateValues = data;
    return this;
  }

  /**
   * DELETE statement
   */
  delete(table: string): this {
    this.queryType = 'DELETE';
    this.fromTable = table;
    return this;
  }

  /**
   * Build and execute the query
   */
  async execute<T extends QueryResultRow = any>(): Promise<QueryResult<T>> {
    const { sql, params } = this.build();
    return await this.client.query<T>(sql, params);
  }

  /**
   * Build the SQL query
   */
  build(): { sql: string; params: any[] } {
    const params: any[] = [];
    let sql = '';

    switch (this.queryType) {
      case 'SELECT':
        sql = this.buildSelectQuery(params);
        break;
      case 'INSERT':
        sql = this.buildInsertQuery(params);
        break;
      case 'UPDATE':
        sql = this.buildUpdateQuery(params);
        break;
      case 'DELETE':
        sql = this.buildDeleteQuery(params);
        break;
      default:
        throw new Error('Query type not specified');
    }

    return { sql, params };
  }

  private buildSelectQuery(params: any[]): string {
    let sql = `SELECT ${this.selectColumns.join(', ')}`;

    if (this.fromTable) {
      sql += ` FROM ${this.fromTable}`;
    }

    // Add JOINs
    for (const join of this.joins) {
      sql += ` ${join.type} JOIN ${join.table} ON ${join.on}`;
    }

    // Add WHERE
    if (this.whereConditions.length > 0) {
      sql += ' WHERE ' + this.buildWhereClause(this.whereConditions, params);
    }

    // Add GROUP BY
    if (this.groupByColumns.length > 0) {
      sql += ` GROUP BY ${this.groupByColumns.join(', ')}`;
    }

    // Add HAVING
    if (this.havingConditions.length > 0) {
      sql += ' HAVING ' + this.buildWhereClause(this.havingConditions, params);
    }

    // Add ORDER BY
    if (this.orderByConditions.length > 0) {
      const orderBy = this.orderByConditions
        .map(order => `${order.column} ${order.direction}`)
        .join(', ');
      sql += ` ORDER BY ${orderBy}`;
    }

    // Add LIMIT
    if (this.limitCount !== null) {
      sql += ` LIMIT $${params.length + 1}`;
      params.push(this.limitCount);
    }

    // Add OFFSET
    if (this.offsetCount !== null) {
      sql += ` OFFSET $${params.length + 1}`;
      params.push(this.offsetCount);
    }

    return sql;
  }

  private buildInsertQuery(params: any[]): string {
    const columns = Object.keys(this.insertValues);
    const placeholders = columns.map((_, i) => `$${params.length + i + 1}`);

    params.push(...Object.values(this.insertValues));

    return `INSERT INTO ${this.fromTable} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`;
  }

  private buildUpdateQuery(params: any[]): string {
    const sets = Object.keys(this.updateValues).map((key, i) => {
      return `${key} = $${params.length + i + 1}`;
    });

    params.push(...Object.values(this.updateValues));

    let sql = `UPDATE ${this.fromTable} SET ${sets.join(', ')}`;

    if (this.whereConditions.length > 0) {
      sql += ' WHERE ' + this.buildWhereClause(this.whereConditions, params);
    }

    return sql;
  }

  private buildDeleteQuery(params: any[]): string {
    let sql = `DELETE FROM ${this.fromTable}`;

    if (this.whereConditions.length > 0) {
      sql += ' WHERE ' + this.buildWhereClause(this.whereConditions, params);
    }

    return sql;
  }

  private buildWhereClause(conditions: WhereCondition[], params: any[]): string {
    return conditions
      .map(condition => {
        if (condition.operator === 'IS NULL' || condition.operator === 'IS NOT NULL') {
          return `${condition.column} ${condition.operator}`;
        } else {
          params.push(condition.value);
          return `${condition.column} ${condition.operator} $${params.length}`;
        }
      })
      .join(' AND ');
  }
}
