<?php

declare(strict_types=1);

namespace Drupal\siteimprove_accessibility\Repository;

use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\taxonomy\TermInterface;

/**
 * OccurrenceRepository service to operate with Occurrence entity.
 */
final class OccurrenceRepository {

  use LoggerChannelTrait;

  const SI_RULES_VOCABULARY = 'siteimprove_accessibility_rules';

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

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * Constructs a new OccurrenceRepository object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Database\Connection $connection
   *   Database connection service.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    Connection $connection,
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->connection = $connection;
  }

  /**
   * Deletes occurrences by AlfaScan entity reference.
   *
   * @param \Drupal\Core\Entity\EntityInterface $scan
   *   AlfaScan entity.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function deleteOccurrences(EntityInterface $scan) {
    $occurrences = $this->entityTypeManager
      ->getStorage('si_occurrence')
      ->loadByProperties(['scan_id' => $scan->id()]);

    if (empty($occurrences)) {
      return;
    }

    foreach ($occurrences as $occurrence) {
      if ($occurrence instanceof ContentEntityInterface) {
        try {
          $occurrence->delete();
        }
        catch (EntityStorageException $e) {
          $this->getLogger('siteimprove_accessibility')
            ->error($e->getMessage());
        }
      }
    }
  }

  /**
   * Creates or updates an SI Rules taxonomy term for Alfa Scan.
   *
   * @param string $rule
   *   The name of the rule.
   * @param string $conformance
   *   The conformance level.
   *
   * @return \Drupal\taxonomy\TermInterface|null
   *   The created or updated taxonomy term, or NULL on failure.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function createUpdateRule(string $rule, string $conformance): ?TermInterface {
    $term_storage = $this->entityTypeManager->getStorage('taxonomy_term');

    $terms = $term_storage->loadByProperties([
      'vid' => self::SI_RULES_VOCABULARY,
      'name' => $rule,
    ]);

    $term = reset($terms) ?: $term_storage->create([
      'vid' => self::SI_RULES_VOCABULARY,
      'name' => $rule,
    ]);

    $term->set('field_si_conformance', $conformance);

    try {
      $term->save();
    }
    catch (EntityStorageException $e) {
      $this->getLogger('siteimprove_accessibility')->error($e->getMessage());
      return NULL;
    }

    return $term;
  }

  /**
   * Creates an Occurrence entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $scan
   *   The scan entity reference.
   * @param \Drupal\taxonomy\TermInterface $rule
   *   The taxonomy term for the rule.
   * @param int $occurrenceCount
   *   The number of times the issue occurs.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface|null
   *   The created Occurrence entity, or NULL if creation fails.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function createOccurrence(EntityInterface $scan, TermInterface $rule, int $occurrenceCount): ?ContentEntityInterface {
    $storage = $this->entityTypeManager->getStorage('si_occurrence');

    $occurrence = $storage->create([
      'scan_id' => $scan->id(),
      'rule_id' => $rule->id(),
      'occurrence' => $occurrenceCount,
    ]);

    try {
      $occurrence->save();
    }
    catch (EntityStorageException $e) {
      $this->getLogger('siteimprove_accessibility')->error($e->getMessage());
      return NULL;
    }

    return $occurrence;
  }

  /**
   * Finds all issues grouped by rule.
   *
   * @return array
   *   An array of issues.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function findAllIssues(): array {

    $query = $this->connection->select('siteimprove_accessibility_occurrence', 'o');

    $query->addField('t', 'name', 'rule');
    $query->addField('tc', 'field_si_conformance_value', 'conformance');
    $query->addExpression('SUM(o.occurrence)', 'occurrences');

    $query->join('taxonomy_term_field_data', 't', 'o.rule_id = t.tid');
    $query->leftJoin('taxonomy_term__field_si_conformance', 'tc', 't.tid = tc.entity_id');

    $query->groupBy('t.tid');
    $query->groupBy('t.name');
    $query->groupBy('tc.field_si_conformance_value');

    $results = $query->execute()->fetchAll(\PDO::FETCH_ASSOC);

    return $results;
  }

  /**
   * Retrieves issues with page counts.
   *
   * @return array
   *   List of issues with total occurrences and affected pages.
   */
  public function findIssuesWithPages(): array {
    $query = $this->connection->select('siteimprove_accessibility_occurrence', 'o');
    $query->fields('t', ['tid', 'name']);
    $query->addExpression('SUM(o.occurrence)', 'occurrences');
    $query->addExpression('COUNT(DISTINCT s.id)', 'pages');

    $query->join('taxonomy_term_field_data', 't', 'o.rule_id = t.tid');
    $query->join('siteimprove_accessibility_alfa_scan', 's', 's.id = o.scan_id');

    $query->groupBy('t.tid');
    $query->groupBy('t.name');

    $results = $query->execute()->fetchAll();

    return array_map([$this, 'castIssueAttributes'], $results);
  }

  /**
   * Prepares Issue data.
   *
   * @param object $issue
   *   Issue results.
   *
   * @return array
   *   Formatted Issue data.
   */
  private function castIssueAttributes(\stdClass $issue): array {
    $issue->id = (int) $issue->tid;
    $issue->occurrences = (int) $issue->occurrences;
    $issue->pages = (int) $issue->pages;
    $issue->rule = (string) $issue->name;

    return (array) $issue;
  }

}
