<?php namespace MapGuesser\Database\Query;

use MapGuesser\Interfaces\Database\IConnection;
use MapGuesser\Database\Utils;

class Modify
{
    private IConnection $connection;

    private string $table;

    private string $idName = 'id';

    private array $attributes = [];

    private ?string $externalId = null;

    private bool $autoIncrement = true;

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

    public function setIdName(string $idName): Modify
    {
        $this->idName = $idName;

        return $this;
    }

    public function setExternalId($id): Modify
    {
        $this->externalId = $id;

        return $this;
    }

    public function setAutoIncrement(bool $autoIncrement = true): Modify
    {
        $this->autoIncrement = $autoIncrement;

        return $this;
    }

    public function fill(array $attributes): Modify
    {
        $this->attributes = array_merge($this->attributes, $attributes);

        return $this;
    }

    public function set(string $name, $value): Modify
    {
        $this->attributes[$name] = $value;

        return $this;
    }

    public function setId($id): Modify
    {
        $this->attributes[$this->idName] = $id;

        return $this;
    }

    public function getId()
    {
        return $this->attributes[$this->idName];
    }

    public function save(): void
    {
        if (isset($this->attributes[$this->idName])) {
            $this->update();
        } else {
            $this->insert();
        }
    }

    public function delete(): void
    {
        if (!isset($this->attributes[$this->idName])) {
            throw new \Exception('No primary key specified!');
        }

        $query = 'DELETE FROM ' . Utils::backtick($this->table) . ' WHERE ' . Utils::backtick($this->idName) . '=?';

        $stmt = $this->connection->prepare($query);
        $stmt->execute([$this->idName => $this->attributes[$this->idName]]);
    }

    private function insert(): void
    {
        if ($this->externalId !== null) {
            $this->attributes[$this->idName] = $this->externalId;
        } elseif (!$this->autoIncrement) {
            $this->attributes[$this->idName] = $this->generateKey();
        }

        $set = $this->generateColumnsWithBinding(array_keys($this->attributes));

        $query = 'INSERT INTO ' . Utils::backtick($this->table) . ' SET ' . $set;

        $stmt = $this->connection->prepare($query);
        $stmt->execute($this->attributes);

        if ($this->autoIncrement) {
            $this->attributes[$this->idName] = $this->connection->lastId();
        }
    }

    private function update(): void
    {
        $attributes = $this->attributes;
        unset($attributes[$this->idName]);

        $set = $this->generateColumnsWithBinding(array_keys($attributes));

        $query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';

        $stmt = $this->connection->prepare($query);
        $stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]]));
    }

    public static function generateColumnsWithBinding(array $columns): string
    {
        array_walk($columns, function(&$value, $key) {
            $value = Utils::backtick($value) . '=?';
        });

        return implode(',', $columns);
    }

    private function generateKey(): string
    {
        return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
    }
}