<?php

declare(strict_types=1);

namespace Drupal\siteimprove_accessibility;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\siteimprove_accessibility\Repository\AlfaScanRepository;
use Drupal\siteimprove_accessibility\Repository\OccurrenceRepository;

/**
 * DailyStatsProcessor prepared data for the report.
 */
class DailyStatsProcessor {

  use LoggerChannelTrait;

  /**
   * AlfaScan repository service.
   *
   * @var \Drupal\siteimprove_accessibility\Repository\AlfaScanRepository
   */
  protected AlfaScanRepository $alfaScanRepository;

  /**
   * Occurrence repository service.
   *
   * @var \Drupal\siteimprove_accessibility\Repository\OccurrenceRepository
   */
  protected OccurrenceRepository $occurrenceRepository;

  /**
   * Time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected TimeInterface $time;

  /**
   * DataFormatter service.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected DateFormatterInterface $dateFormatter;

  /**
   * Constructs DailyStatsProcessor.
   */
  public function __construct(
    AlfaScanRepository $alfa_scan_repository,
    OccurrenceRepository $occurrence_repository,
    TimeInterface $time,
    DateFormatterInterface $date_formatter,
  ) {
    $this->alfaScanRepository = $alfa_scan_repository;
    $this->occurrenceRepository = $occurrence_repository;
    $this->time = $time;
    $this->dateFormatter = $date_formatter;
  }

  /**
   * Gets aggregated issues by rules.
   *
   * @return array
   *   Aggregated issues by rules.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getAggregatedIssues(): array {
    $scan_count = $this->alfaScanRepository->countAllScans();
    $issues = $this->occurrenceRepository->findAllIssues();

    $aggregated_stats = [
      'scans' => $scan_count,
      'rules' => [],
    ];

    foreach ($issues as $issue) {
      $rule = $issue['rule'];
      $conformance = $issue['conformance'];
      $occurrences = $issue['occurrences'];
      $aggregated_stats['rules'][$rule][$conformance] =
        ($aggregated_stats['rules'][$rule][$conformance] ?? 0) + $occurrences;
    }

    return $aggregated_stats;

  }

  /**
   * Processes daily stats, ensuring missing dates are filled.
   *
   * @param array $results
   *   List of stats with timestamps.
   *
   * @return object
   *   Prepared daily stats.
   */
  public function prepareDailyStats(array $results): \stdClass {
    $daily_stats = new \stdClass();
    $daily_stats->issues = [];
    $daily_stats->occurrences = [];

    if (empty($results)) {
      return $daily_stats;
    }

    $currentTimestamp = (int) $results[0]['date'];

    foreach ($results as $result) {
      $stats = $result['aggregated_stats'];

      while ($currentTimestamp < (int) $result['date']) {
        $daily_stats = $this->fillEmptyDate($daily_stats, $currentTimestamp, 'issues');
        $daily_stats = $this->fillEmptyDate($daily_stats, $currentTimestamp, 'occurrences');
        $currentTimestamp = $this->nextDay($currentTimestamp);
      }

      [$daily_stats->issues[], $daily_stats->occurrences[]] =
        $this->prepareStatRecord($stats, (int) $result['date']);

      $currentTimestamp = $this->nextDay((int) $result['date']);
    }

    // Fill in missing dates until today’s midnight (to include today’s scans).
    $endTimestamp = strtotime('midnight', $this->time->getRequestTime());

    while ($currentTimestamp <= $endTimestamp) {
      $daily_stats = $this->fillEmptyDate($daily_stats, $currentTimestamp, 'issues');
      $daily_stats = $this->fillEmptyDate($daily_stats, $currentTimestamp, 'occurrences');
      $currentTimestamp = $this->nextDay($currentTimestamp);
    }

    return $daily_stats;
  }

  /**
   * Fills in missing dates with the last known data.
   *
   * @param object $daily_stats
   *   StdClass for storing DailyStats data.
   * @param int $timestamp
   *   Date to operate on in timestamp.
   * @param string $dataKey
   *   Issue or occurrence.
   *
   * @return object
   *   Prepared daily stats.
   */
  private function fillEmptyDate(\stdClass $daily_stats, int $timestamp, string $dataKey): \stdClass {
    if (!empty($daily_stats->$dataKey)) {
      $lastData = end($daily_stats->$dataKey);
      $lastData['date'] = $this->dateFormatter->format($timestamp, 'custom', 'Y-m-d');
      $daily_stats->$dataKey[] = $lastData;
    }
    return $daily_stats;
  }

  /**
   * Moves to the next day's timestamp (always midnight).
   *
   * @param int $timestamp
   *   Date.
   *
   * @return int
   *   Next day in timestamp format.
   */
  private function nextDay(int $timestamp): int {
    return strtotime('midnight +1 day', $timestamp);
  }

  /**
   * Processes a single stat record into the required format.
   *
   * @param array $stats
   *   Aggregated stats from storage.
   * @param int $timestamp
   *   The timestamp for the record.
   *
   * @return array
   *   Prepared daily stat record.
   */
  public function prepareStatRecord(array $stats, int $timestamp): array {
    $issues = [];
    $occurrences = [];

    foreach ($stats['rules'] as $level_group) {
      foreach ($level_group as $level => $amount) {
        $issues[$level] = ($issues[$level] ?? 0) + 1;
        $occurrences[$level] = ($occurrences[$level] ?? 0) + $amount;
      }
    }

    $formattedDate = $this->dateFormatter->format($timestamp, 'custom', 'Y-m-d');

    return [
      [
        'date' => $formattedDate,
        'pages' => $stats['scans'],
        'conformance' => $issues,
      ],
      [
        'date' => $formattedDate,
        'pages' => $stats['scans'],
        'conformance' => $occurrences,
      ],
    ];
  }

}
