<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\Adapter\Database;

use Drupal\Core\Database\Connection;
use Drupal\languagewire_translation_provider\Database\DocumentRepositoryInterface;
use Drupal\languagewire_translation_provider\Database\Mapper\DocumentMapper;
use Drupal\languagewire_translation_provider\Database\TablesInterface;
use Drupal\languagewire_translation_provider\Domain\Document;

/**
 * Drupal Document Repository.
* */
final class DocumentRepository implements DocumentRepositoryInterface {

  /**
   * Connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  private Connection $connection;

  /**
   * Mapper.
   *
   * @var \Drupal\languagewire_translation_provider\Database\Mapper\DocumentMapper
   */
  private DocumentMapper $mapper;

  /**
   * Constructs a new DocumentRepository object.
   *
   * @param \Drupal\Core\Database\Connection $connection
   *   Connection.
   * @param \Drupal\languagewire_translation_provider\Database\Mapper\DocumentMapper $mapper
   *   Mapper.
   */
  public function __construct(Connection $connection, DocumentMapper $mapper) {
    $this->connection = $connection;
    $this->mapper = $mapper;
  }

  /**
   * Get by ID.
   *
   * @throws \Exception
   */
  public function getById(int $documentId): ?Document {
    $dbItem = $this->connection->select(TablesInterface::DOCUMENTS, 'docs')->fields('docs')->condition('did', $documentId)->execute()->fetchAssoc();
    return $dbItem ? $this->mapper->toDocument($dbItem) : NULL;
  }

  /**
   * Get all by project ID.
   *
   * @throws \Exception
   */
  public function getAllByProjectId(int $projectId): array {
    $dbItems = $this->connection->select(TablesInterface::DOCUMENTS, 'docs')->fields('docs')->condition('project_id', $projectId)->execute()->fetchAll(\PDO::FETCH_ASSOC);
    $documents = array_map(function (array $dbItem): ?Document {
      return $this->mapper->toDocument($dbItem);
    }, $dbItems);
    // @todo return [$dbItem, $result] in $documents and then cleanup invalid dbItems
    return array_filter($documents, function (?Document $result): bool {
      return $result != NULL;
    });
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function save(Document $document): Document {
    // Drupal offers the upsert and merge queries which would
    // do insert/update for us but neither of them will.
    // Return the last insert ID. There's no _clean_ way of receiving it,
    // so we might as well do it ourselves.
    // Should we discover that we don't need to return anything from
    // those save methods we can optimize it easily.
    $documentId = $document->getId() ? $this->update($document) : $this->insert($document);
    return $this->getById($documentId);
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function saveMany(array $documents): array {
    // This is a very naive and suboptimal
    // implementation but gets the job done...
    return array_map(function (Document $document): Document {
      return $this->save($document);
    }, $documents);
  }

  /**
   * Insert document into database.
   *
   * @throws \Exception
   */
  private function insert(Document $document): int {
    $dbEntity = $this->mapper->toDatabaseEntity($document);
    return (int) $this->connection->insert(TablesInterface::DOCUMENTS)->fields(array_keys($dbEntity))->values(array_values($dbEntity))->execute();
  }

  /**
   * Update document in database.
   *
   * @throws \Exception
   */
  private function update(Document $document): int {
    $this->connection->update(TablesInterface::DOCUMENTS)->condition('did', $document->getId())->fields($this->mapper->toDatabaseEntity($document))->execute();
    return $document->getId();
  }

}
