<?php

namespace Mautic\LeadBundle\Entity;

use Mautic\CoreBundle\Entity\CommonRepository;

/**
 * @extends CommonRepository<FrequencyRule>
 */
class FrequencyRuleRepository extends CommonRepository
{
    /**
     * @param string      $channel
     * @param array       $leadIds
     * @param string|null $defaultFrequencyNumber
     * @param string|null $defaultFrequencyTime
     * @param string      $statTable
     * @param string      $statSentColumn
     * @param string      $statContactColumn
     */
    public function getAppliedFrequencyRules(
        $channel,
        $leadIds,
        $defaultFrequencyNumber,
        $defaultFrequencyTime,
        $statTable = 'email_stats',
        $statContactColumn = 'lead_id',
        $statSentColumn = 'date_sent'
    ): array {
        if (empty($leadIds)) {
            return [];
        }

        $frequencyRuleViolations = $this->getCustomFrequencyRuleViolations($channel, $leadIds, $statTable, $statContactColumn, $statSentColumn);

        if (!$this->validateDefaultParameters($defaultFrequencyNumber, $defaultFrequencyTime)) {
            // It makes no sense to calculate default rule violations
            // if default parameters are not valid
            return $frequencyRuleViolations;
        }

        $defaultRuleViolations = $this->getDefaultFrequencyRuleViolations($leadIds, $defaultFrequencyNumber, $defaultFrequencyTime, $statTable, $statContactColumn, $statSentColumn);

        return array_merge($frequencyRuleViolations, $defaultRuleViolations);
    }

    private function validateDefaultParameters(mixed $number, mixed $time): bool
    {
        return $number && $time;
    }

    public function getFrequencyRules($channel = null, $leadIds = null): array
    {
        $q = $this->_em->getConnection()->createQueryBuilder();

        $q->select(
            'fr.id, fr.frequency_time, fr.frequency_number, fr.channel, fr.preferred_channel, fr.pause_from_date, fr.pause_to_date, fr.lead_id'
        )
            ->from(MAUTIC_TABLE_PREFIX.'lead_frequencyrules', 'fr');

        if ($channel) {
            $q->andWhere('fr.channel = :channel')
                ->setParameter('channel', $channel);
        }

        $groupByLeads = is_array($leadIds);
        if ($leadIds) {
            if ($groupByLeads) {
                $q->andWhere(
                    $q->expr()->in('fr.lead_id', $leadIds)
                );
            } else {
                $q->andWhere('fr.lead_id = :leadId')
                    ->setParameter('leadId', (int) $leadIds);
            }
        }

        $results = $q->executeQuery()->fetchAllAssociative();

        $frequencyRules = [];

        foreach ($results as $result) {
            if ($groupByLeads) {
                if (!isset($frequencyRules[$result['lead_id']])) {
                    $frequencyRules[$result['lead_id']] = [];
                }

                $frequencyRules[$result['lead_id']][$result['channel']] = $result;
            } else {
                $frequencyRules[$result['channel']] = $result;
            }
        }

        return $frequencyRules;
    }

    public function getPreferredChannel($leadId): array
    {
        $q = $this->_em->getConnection()->createQueryBuilder();

        $q->select('fr.id, fr.frequency_time, fr.frequency_number, fr.channel, fr.pause_from_date, fr.pause_to_date')
            ->from(MAUTIC_TABLE_PREFIX.'lead_frequencyrules', 'fr');
        $q->where('fr.preferred_channel = :preferredChannel')
            ->setParameter('preferredChannel', true, 'boolean');
        if ($leadId) {
            $q->andWhere('fr.lead_id = :leadId')
                ->setParameter('leadId', $leadId);
        }

        return $q->executeQuery()->fetchAllAssociative();
    }

    /**
     * @param string $channel
     * @param string $statTable
     * @param string $statContactColumn
     * @param string $statSentColumn
     */
    private function getCustomFrequencyRuleViolations($channel, array $leadIds, $statTable, $statContactColumn, $statSentColumn): array
    {
        $q = $this->getEntityManager()->getConnection()->createQueryBuilder();

        $q->select("ch.$statContactColumn, fr.frequency_number, fr.frequency_time")
            ->from(MAUTIC_TABLE_PREFIX.$statTable, 'ch')
            ->join('ch', MAUTIC_TABLE_PREFIX.'lead_frequencyrules', 'fr', "ch.{$statContactColumn} = fr.lead_id");

        if ($channel) {
            $q->andWhere('fr.channel = :channel')
                ->setParameter('channel', $channel);
        }

        // Preferred channel is stored in this table so they may not have a frequency rule defined but just a preference so exclude them
        $q->andWhere('fr.frequency_time IS NOT NULL AND fr.frequency_number IS NOT NULL');

        // Calculate the rule timeframe
        $q->andWhere(
            '(ch.'.$statSentColumn.' >= case fr.frequency_time
                 when \'MONTH\' then DATE_SUB(NOW(),INTERVAL 1 MONTH)
                 when \'DAY\' then DATE_SUB(NOW(),INTERVAL 1 DAY)
                 when \'WEEK\' then DATE_SUB(NOW(),INTERVAL 1 WEEK)
                end)'
        );

        $q->andWhere(
            $q->expr()->in("ch.$statContactColumn", $leadIds)
        );

        $q->groupBy("ch.$statContactColumn, fr.frequency_time, fr.frequency_number");

        $q->having("count(ch.$statContactColumn) >= fr.frequency_number");

        return $q->executeQuery()->fetchAllAssociative();
    }

    /**
     * @param string $defaultFrequencyNumber
     * @param string $defaultFrequencyTime
     * @param string $statTable
     * @param string $statContactColumn
     * @param string $statSentColumn
     *
     * @return array
     */
    private function getDefaultFrequencyRuleViolations(
        array $leadIds,
        $defaultFrequencyNumber,
        $defaultFrequencyTime,
        $statTable,
        $statContactColumn,
        $statSentColumn
    ) {
        $query = $this->getEntityManager()->getConnection()->createQueryBuilder();

        $query->select("ch.$statContactColumn")
            ->from(MAUTIC_TABLE_PREFIX.$statTable, 'ch');

        switch ($defaultFrequencyTime) {
            case 'MONTH':
                $since = new \DateTime('-1 month', new \DateTimeZone('UTC'));
                break;
            case 'WEEK':
                $since = new \DateTime('-1 week', new \DateTimeZone('UTC'));
                break;
            case 'DAY':
                $since = new \DateTime('-1 day', new \DateTimeZone('UTC'));
                break;
            default:
                return [];
        }

        $query->andWhere('ch.'.$statSentColumn.' >= :frequencyTime')
            ->setParameter('frequencyTime', $since->format('Y-m-d H:i:s'));

        $query->andWhere(
            $query->expr()->in("ch.$statContactColumn", $leadIds)
        );

        $hasCustomRules = $this->tableHasRows(MAUTIC_TABLE_PREFIX.'lead_frequencyrules');
        // We don't need to check if users have custom rules if there are no records inside that table
        if ($hasCustomRules) {
            // Exclude contacts with custom rules defined
            $subQuery = $this->getEntityManager()->getConnection()->createQueryBuilder();
            $subQuery->select('null')
                ->from(MAUTIC_TABLE_PREFIX.'lead_frequencyrules', 'fr')
                ->where("fr.lead_id = ch.{$statContactColumn}")
                ->andWhere('fr.frequency_time IS NOT NULL AND fr.frequency_number IS NOT NULL');
            $query->andWhere(
                sprintf('NOT EXISTS (%s)', $subQuery->getSQL())
            );
        }

        $query->groupBy("ch.$statContactColumn");

        $query->having("count(ch.$statContactColumn) >= :defaultNumber")
            ->setParameter('defaultNumber', $defaultFrequencyNumber);

        $results = $query->executeQuery()->fetchAllAssociative();

        foreach ($results as $key => $result) {
            $results[$key]['frequency_number'] = $defaultFrequencyNumber;
            $results[$key]['frequency_time']   = $defaultFrequencyTime;
        }

        return $results;
    }
}
