<?php

namespace WKO\Model\Infrastructure;


abstract class Repository
{
	protected string $prefix;
	protected $db;
	protected string $entity;
	protected string $table;

	public function __construct(string $entity, string $table)
	{
		$this->entity = $entity;
		$this->table = $table;
		$this->setTable($this->table);
	}

	private function setTable(string $table): void
	{
		global $wpdb;

		if ($wpdb) {
			$this->db     = $wpdb;
			$this->prefix = $this->db->prefix;
			$this->table  = "{$this->prefix}{$table}";
		}
	}

	protected function create($fields = []): void
	{
		if (empty($fields)) {
			return;
		}

		$rows = [];

		foreach ($fields as $key => $value) {
			$row = $key ? "`$key` " . implode(" ", $value) : implode(" ", $value);
			$rows[] = $row;
		}

		$fields = implode(",", $rows);

		$this->db->query("CREATE TABLE IF NOT EXISTS {$this->table} ( {$fields} );");
	}

	public function down(): void
	{
		$this->drop();
	}

	protected function drop(): void
	{
		$this->db->query("DROP TABLE {$this->table};");
	}

	protected function query($query)
	{
		if (!str_contains((string) $query, 'SELECT')) {
			return $this->db->query($query);
		}

		return $this->db->get_results($query);
	}

	public function save(Entity $entity): bool
	{
		if (isset($entity->id)) {
			$query = $this->update($this->getEntityData($entity), ['id' => $entity->id]);
		} else {
			$query = $this->insert($this->getEntityData($entity));
		}
		return !is_bool($query);
	}

	protected function update($fields, $where)
	{
		return $this->db->update($this->table, $fields, $where);
	}

	protected function insert($fields)
	{
		return $this->db->insert($this->table, $fields);
	}

	public function delete(int $id): bool
	{
		return $this->db->delete($this->table, ['id' => $id]);
	}

	public function findAll(string $orderBy = '', string $order = 'ASC', int $limit = 0, int $page = 1, array $conditions = [1 => 1]): array
	{
		$result = [];
		$conditionPlaceholders = [];
		$conditionValues = [];
		$query = "SELECT * FROM {$this->table}";

		if (!empty($conditions)) {
			foreach ($conditions as $key => $value) {
				$conditionPlaceholders[] = "$key = %s";
				$conditionValues[] = $value;
			}
			$query .= " WHERE " . implode(" AND ", $conditionPlaceholders);
		}
		$preparedQuery = $this->db->prepare($query, ...$conditionValues);

		if ($orderBy) {
			$order = strtoupper($order) === 'DESC' ? 'DESC' : 'ASC';
			$preparedQuery .= " ORDER BY $orderBy $order";
		}


		if ($limit > 0) {
			$result['pagination'] = $this->getPagination($limit, $page, $conditions);
			$offset = $result['pagination']['offset'];

			$preparedQuery .= $this->db->prepare(" LIMIT %d,%d", $offset, $limit);
		}

		$result['rows'] = $this->query($preparedQuery) ?? [];
		$entities = [];
		foreach ($result['rows'] as $row) {
			$entities[] = $this->fill($row);
		}
		$result['rows'] = $entities;
		return $result;
	}

	public function getPagination(int $limit, int $page, array $conditions = []): array
	{
		$query = "SELECT COUNT(*) AS 'rows' FROM {$this->table}";

		if (!empty($conditions)) {
			foreach ($conditions as $key => $value) {
				$conditionPlaceholders[] = "$key = %s";
				$conditionValues[] = $value;
			}
			$query .= " WHERE " . implode(" AND ", $conditionPlaceholders);
		}
		$preparedQuery = $this->db->prepare($query, ...$conditionValues);

		$result = $this->query($preparedQuery);

		if ($result) {
			$count = array_shift($result);

			$pages   = (int) ceil($count->rows / $limit);
			$current = min($page, $pages);
			$previous = ($current - 1) > 0 ? ($current - 1) : $current;
			$next    = min(($current + 1), $pages);

			return [
				'pages'    => $pages,
				'current'  => $current,
				'previous' => $previous,
				'next'     => $next,
				'offset'   => ($page - 1) * $limit,
				'rows'     => $count->rows
			];
		}

		return [];
	}

	public function findById(int $id)
	{
		$result = $this->query($this->db->prepare("SELECT * FROM {$this->table} WHERE id = %d;", $id));

		try {
			$row = array_shift($result);
			if (!($row instanceof \stdClass)) {
				return false;
			}

			return $this->fill($row);
		} catch (\Exception $e) {
			return false;
		}
	}

	abstract protected function getEntityData(Entity $entity): array;

	abstract protected function fill(\stdClass $row);
}
