<?php

namespace Booktics\Abstracts;

use Booktics\Contracts\Database_Interface;
use RuntimeException;
use wpdb;

/**
 * Database Manager for booktics database
 *
 * @package Booktics/Abstracts
 */
class Booktics_Database implements Database_Interface {

    /**
     * WordPress database global variable
     *
     * @var wpdb
     */
    private $db;

    /**
     * Set global wpdb instance
     */
    public function __construct() {
        global $wpdb;
        if ( ! $wpdb instanceof wpdb ) {
            throw new RuntimeException( 'WordPress database object not available' );
        }
        $this->db = $wpdb;
    }

    /**
     * Begin database transaction
     *
     * @return void
     */
    public function begin_transaction(): void {
        $this->db->query( 'START TRANSACTION' );
    }

    /**
     * Commit transactions to database
     *
     * @return void
     */
    public function commit(): void {
        $this->db->query( 'COMMIT' );
    }

    /**
     * Rollback uncommited transactions
     *
     * @return void
     */
    public function roll_back(): void {
        $this->db->query( 'ROLLBACK' );
    }

    /**
     * Delete records of specific table
     *
     * @param string $table
     * @param array $where
     *
     * @return void
     */
    public function delete( string $table, array $where ): void {
        $table      = booktics_get_table_name( $table );
        $conditions = array();
        $values     = array();

        foreach ( $where as $key => $value ) {
            $conditions[] = "$key = %s";
            $values[]     = $value;
        }

        $condition_string = implode( ' AND ', $conditions );

        $sql = "DELETE FROM $table WHERE $condition_string";

        $result = $this->db->query(
            $this->db->prepare( $sql, ...$values )
        );

        if ( $result === false ) {
            throw new RuntimeException(
            // translators: %s is the database error message.
                sprintf( esc_html__( 'Failed to delete records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
            );
        }
    }

    /**
     * Batch insert records
     *
     * @param string $table
     * @param array $records
     *
     * @return void
     */
    public function insert_batch( string $table, array $records ): void {
        if ( empty( $records ) ) {
            return;
        }

        $table              = booktics_get_table_name( $table );
        $columns            = array_keys( $records[0] );
        $placeholders       = array_fill( 0, count( $columns ), '%s' );
        $placeholder_string = '(' . implode( ',', $placeholders ) . ')';

        $values              = array();
        $placeholders_groups = array();

        foreach ( $records as $record ) {
            $placeholders_groups[] = $placeholder_string;
            foreach ( $columns as $column ) {
                $values[] = $record[ $column ] ?? '';
            }
        }

        $column_string       = implode( ',', $columns );
        $placeholders_string = implode( ',', $placeholders_groups );

        $sql = "INSERT INTO $table ($column_string) VALUES $placeholders_string";

        $result = $this->db->query(
            $this->db->prepare( $sql, ...$values )
        );

        if ( $result === false ) {
            // translators: %s is the database error message.
            sprintf( esc_html__( 'Failed to insert records: %s', 'booktics' ), esc_html( $this->db->last_error ) );
        }
    }

    /**
     * Select records from a table
     *
     * @param string $table
     * @param array $where Optional WHERE conditions
     * @param array $columns Columns to select
     *
     * @return array
     */
    public function select( string $table, array $where = array(), array $columns = array( '*' ) ) {
        $table     = booktics_get_table_name( $table );
        $columns_s = implode( ', ', $columns );
        $sql       = "SELECT $columns_s FROM $table";

        $values = array();
        if ( ! empty( $where ) ) {
            $conditions = array();

            foreach ( $where as $key => $value ) {
                $conditions[] = "$key = %s";
                $values[]     = $value;
            }

            $condition_string = implode( ' AND ', $conditions );
            $sql              .= " WHERE $condition_string";
        }

        $prepared = $this->db->prepare( $sql, ...$values );
        $results  = $this->db->get_results( $prepared );

        if ( $results === null ) {
            throw new RuntimeException(
            // translators: %s is the database error message.
                sprintf( esc_html__( 'Failed to select records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
            );
        }

        return $results;
    }

    /**
     * Initialize query builder instance
     *
     * @param string $table
     *
     * @return DB_Query_Builder
     */
    public function table( string $table ): DB_Query_Builder {
        return ( new DB_Query_Builder( $this->db ) )->table( $table );
    }

    /**
     * Update records in a table
     *
     * @param string $table
     * @param array $data
     * @param array $where
     *
     * @return void
     */
    public function update( string $table, array $data, array $where ) {
        $table            = booktics_get_table_name( $table );
        $set_values       = array();
        $where_conditions = array();
        $values           = array();

        foreach ( $data as $key => $value ) {
            $set_values[] = "$key = %s";
            $values[]     = $value;
        }

        foreach ( $where as $key => $value ) {
            $where_conditions[] = "$key = %s";
            $values[]           = $value;
        }

        $set_string   = implode( ', ', $set_values );
        $where_string = implode( ' AND ', $where_conditions );

        $sql = "UPDATE $table SET $set_string WHERE $where_string";

        $result = $this->db->query(
            $this->db->prepare( $sql, ...$values )
        );

        if ( $result === false ) {
            throw new RuntimeException(
            // translators: %s: Records Name
                sprintf( esc_html__( 'Failed to update records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
            );
        }
    }
}
