<?php
/**
 * Cart Repository.
 *
 * All cart DB read/write flows through here.
 * The CartManager calls this layer for persistence;
 * no direct DB calls appear in CartManager.
 *
 * @package TitanCart\Modules\Cart
 */

namespace TitanCart\Modules\Cart;

use TitanCart\DB\Database;

defined( 'ABSPATH' ) || exit;

class CartRepository {

    private Database $db;
    private string   $t_carts;
    private string   $t_items;

    public function __construct() {
        $this->db      = titancart_db();
        $this->t_carts = $this->db->table( 'carts' );
        $this->t_items = $this->db->table( 'cart_items' );
    }

    // ── Cart row ──────────────────────────────────────────────────────────────

    /**
     * Find or create a cart row by session key.
     *
     * @param  string   $session_key
     * @param  int|null $customer_id  If provided, also tag the cart with customer ID.
     * @return array<string,mixed> Cart row.
     */
    public function find_or_create( string $session_key, ?int $customer_id = null ): array {
        $cart = $this->get_by_session( $session_key );

        if ( $cart ) {
            // If we now have a customer ID and the row doesn't, update it.
            if ( $customer_id && empty( $cart['customer_id'] ) ) {
                $this->db->update( 'carts', [ 'customer_id' => $customer_id ], [ 'id' => $cart['id'] ] );
                $cart['customer_id'] = $customer_id;
            }
            return $cart;
        }

        $id = $this->db->insert( 'carts', [
            'session_key' => $session_key,
            'customer_id' => $customer_id,
            'currency'    => $this->default_currency(),
        ] );

        return $this->get_by_id( $id );
    }

    /**
     * Get a cart by session key.
     *
     * @param  string $session_key
     * @return array<string,mixed>|null
     */
    public function get_by_session( string $session_key ): ?array {
        return $this->db->getRow(
            "SELECT * FROM {$this->t_carts} WHERE session_key = ?s LIMIT 1",
            $session_key
        ) ?: null;
    }

    /**
     * Get a cart by its primary key.
     *
     * @param  int $cart_id
     * @return array<string,mixed>|null
     */
    public function get_by_id( int $cart_id ): ?array {
        return $this->db->getRow(
            "SELECT * FROM {$this->t_carts} WHERE id = ?i LIMIT 1",
            $cart_id
        ) ?: null;
    }

    /**
     * Get the most recent active cart for a customer ID.
     *
     * @param  int $customer_id
     * @return array<string,mixed>|null
     */
    public function get_by_customer( int $customer_id ): ?array {
        return $this->db->getRow(
            "SELECT * FROM {$this->t_carts}
             WHERE customer_id = ?i
             ORDER BY updated_at DESC
             LIMIT 1",
            $customer_id
        ) ?: null;
    }

    /**
     * Update cart-level fields (coupon_code, notes, currency, meta).
     *
     * @param int   $cart_id
     * @param array $data
     */
    public function update_cart( int $cart_id, array $data ): void {
        $allowed = [ 'coupon_code', 'notes', 'currency', 'meta' ];
        $row     = array_intersect_key( $data, array_flip( $allowed ) );

        if ( isset( $row['meta'] ) && is_array( $row['meta'] ) ) {
            $row['meta'] = wp_json_encode( $row['meta'] );
        }

        if ( ! empty( $row ) ) {
            $this->db->update( 'carts', $row, [ 'id' => $cart_id ] );
        }
    }

    /**
     * Re-assign a cart to a different session key (used after login merge).
     *
     * @param int    $cart_id
     * @param string $new_session_key
     */
    public function reassign_session( int $cart_id, string $new_session_key ): void {
        $this->db->update( 'carts', [ 'session_key' => $new_session_key ], [ 'id' => $cart_id ] );
    }

    /**
     * Delete a cart row and all its items (hard delete — used post-order).
     *
     * @param int $cart_id
     */
    public function delete_cart( int $cart_id ): void {
        $this->db->delete( 'cart_items', [ 'cart_id' => $cart_id ] );
        $this->db->delete( 'carts',      [ 'id'      => $cart_id ] );
    }

    // ── Cart items ────────────────────────────────────────────────────────────

    /**
     * Get all items for a cart, ordered by insertion time.
     *
     * @param  int $cart_id
     * @return array<int, array<string,mixed>>
     */
    public function get_items( int $cart_id ): array {
        return $this->db->getArray(
            "SELECT * FROM {$this->t_items}
             WHERE cart_id = ?i
             ORDER BY added_at ASC",
            $cart_id
        );
    }

    /**
     * Find an existing line item matching product + variation + options hash.
     *
     * @param  int    $cart_id
     * @param  int    $product_id
     * @param  int    $variation_id
     * @param  string $options_hash  MD5 of normalised options JSON.
     * @param  string $meta_hash     MD5 of normalised meta JSON.
     * @return array<string,mixed>|null
     */
    public function find_item(
        int $cart_id,
        int $product_id,
        int $variation_id,
        string $options_hash,
        string $meta_hash = ''
    ): ?array {
        // If no meta_hash provided, default to empty-string hash for backward compat.
        if ( $meta_hash === '' ) {
            $meta_hash = md5( '' );
        }
        return $this->db->getRow(
            "SELECT * FROM {$this->t_items}
             WHERE cart_id    = ?i
               AND product_id = ?i
               AND COALESCE(variation_id, 0) = ?i
               AND MD5(COALESCE(options, '')) = ?s
               AND MD5(COALESCE(meta, ''))    = ?s
             LIMIT 1",
            $cart_id,
            $product_id,
            $variation_id,
            $options_hash,
            $meta_hash
        ) ?: null;
    }

    /**
     * Add a new line item.
     *
     * @param  int   $cart_id
     * @param  array $data
     * @return int New item ID.
     */
    public function add_item( int $cart_id, array $data ): int {
        return $this->db->insert( 'cart_items', [
            'cart_id'      => $cart_id,
            'product_id'   => (int) $data['product_id'],
            'variation_id' => ! empty( $data['variation_id'] ) ? (int) $data['variation_id'] : null,
            'quantity'     => (int) $data['quantity'],
            'unit_price'   => number_format( (float) $data['unit_price'], 4, '.', '' ),
            'options'      => ! empty( $data['options'] ) ? wp_json_encode( $data['options'] ) : null,
            'meta'         => ! empty( $data['meta'] )    ? wp_json_encode( $data['meta'] )    : null,
        ] );
    }

    /**
     * Update quantity and/or price of an existing item.
     *
     * @param int   $item_id
     * @param array $data   Keys: quantity, unit_price, options.
     */
    public function update_item( int $item_id, array $data ): void {
        $row = [];

        if ( isset( $data['quantity'] ) ) {
            $row['quantity'] = (int) $data['quantity'];
        }
        if ( isset( $data['unit_price'] ) ) {
            $row['unit_price'] = number_format( (float) $data['unit_price'], 4, '.', '' );
        }
        if ( isset( $data['options'] ) ) {
            $row['options'] = is_array( $data['options'] )
                ? wp_json_encode( $data['options'] )
                : $data['options'];
        }

        if ( ! empty( $row ) ) {
            $this->db->update( 'cart_items', $row, [ 'id' => $item_id ] );
        }
    }

    /**
     * Remove an item by its ID.
     *
     * @param int $item_id
     */
    public function remove_item( int $item_id ): void {
        $this->db->delete( 'cart_items', [ 'id' => $item_id ] );
    }

    /**
     * Remove all items from a cart.
     *
     * @param int $cart_id
     */
    public function clear_items( int $cart_id ): void {
        $this->db->delete( 'cart_items', [ 'cart_id' => $cart_id ] );
    }

    /**
     * Return total item count (sum of all quantities) for a cart.
     *
     * @param  int $cart_id
     * @return int
     */
    public function count_items( int $cart_id ): int {
        return (int) $this->db->getField(
            "SELECT COALESCE(SUM(quantity), 0) FROM {$this->t_items} WHERE cart_id = ?i",
            $cart_id
        );
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private function default_currency(): string {
        return (string) $this->db->getField(
            "SELECT code FROM {$this->db->table('currencies')} WHERE is_primary = 1 LIMIT 1"
        ) ?: 'USD';
    }
}
