<?php
/*********************************************************************************
 * The contents of this file are subject to the EspoCRM Advanced Pack
 * Agreement ("License") which can be viewed at
 * https://www.espocrm.com/advanced-pack-agreement.
 * By installing or using this file, You have unconditionally agreed to the
 * terms and conditions of the License, and You may not use this file except in
 * compliance with the License.  Under the terms of the license, You shall not,
 * sublicense, resell, rent, lease, distribute, or otherwise  transfer rights
 * or usage to the software.
 *
 * Copyright (C) 2015-2023 Letrium Ltd.
 *
 * License ID: 7f33790bc71ff8ca9dfbc9066dbc323a
 ***********************************************************************************/

namespace Espo\Modules\Advanced\Reports;

use Espo\Core\Field\DateTime;
use Espo\Core\Select\SearchParams;
use Espo\Core\Select\SelectBuilderFactory;
use Espo\Core\Select\Where\Item as WhereItem;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use Espo\Modules\Advanced\Tools\Report\GridType\Result;
use Espo\Modules\Advanced\Tools\Report\ListType\Result as ListResult;
use Espo\Modules\Advanced\Tools\Report\ListType\SubReportParams;
use Espo\Modules\Crm\Entities\Call;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Condition as C;
use Espo\ORM\Query\Part\Expression as E;
use Espo\ORM\Query\Part\Order;
use Espo\ORM\Query\Part\WhereItem as WherePart;
use Espo\ORM\Query\SelectBuilder;

class LeadsByLastActivity implements GridReport
{
    /** @var array<int, ?array{int, ?int}> */
    private array $rangeList = [
        [0, 7],
        [7, 15],
        [15, 30],
        [30, 60],
        [60, 120],
        [120, null],
        null,
    ];

    /** @var string[] */
    private array $ignoreStatusList;

    private EntityManager $entityManager;
    private Metadata $metadata;
    private Language $language;
    private SelectBuilderFactory $selectBuilderFactory;

    public function __construct(
        EntityManager $entityManager,
        Metadata $metadata,
        Language $language,
        SelectBuilderFactory $selectBuilderFactory
    ) {
        $this->entityManager = $entityManager;
        $this->metadata = $metadata;
        $this->language = $language;
        $this->selectBuilderFactory = $selectBuilderFactory;

        $this->ignoreStatusList = $this->metadata
            ->get(['entityDefs', 'Lead', 'fields', 'status', 'notActualOptions']) ?? [];
    }

    private function executeSubReport(
        SearchParams $searchParams,
        SubReportParams $subReportParams,
        ?User $user
    ): ListResult {

        $groupValue = $subReportParams->getGroupValue();
        $groupIndex = $subReportParams->getGroupIndex();

        $selectBuilder = $this->selectBuilderFactory
            ->create()
            ->from(Lead::ENTITY_TYPE)
            ->withStrictAccessControl()
            ->withSearchParams($searchParams);

        if ($user) {
            $selectBuilder->forUser($user);
        }

        $queryBuilder = $selectBuilder->buildQueryBuilder();

        if (!$groupIndex) {
            if ($groupValue === '-') {
                $range = null;
            } else {
                $range = explode('-', $groupValue);

                if (empty($range[1])) {
                    $range[1] = null;
                }
            }
        }

        if (!$groupIndex) {
            $queryBuilder->where(
                $this->getWherePart($range)
            );

            $queryBuilder->where(['status!=' => $this->ignoreStatusList]);

            if ($subReportParams->hasGroupValue2()) {
                $queryBuilder->where(['status' => $subReportParams->getGroupValue2()]);
            }
        }
        else {
            $queryBuilder->where(['status' => $groupValue]);
        }

        $query = $queryBuilder->build();

        $collection = $this->entityManager
            ->getRDBRepository(Lead::ENTITY_TYPE)
            ->clone($query)
            ->find();

        $count =  $this->entityManager
            ->getRDBRepository(Lead::ENTITY_TYPE)
            ->clone($query)
            ->count();

        return new ListResult($collection, $count);
    }

    public function runSubReport(SearchParams $searchParams, SubReportParams $subReportParams, ?User $user): ListResult
    {

        return $this->executeSubReport($searchParams, $subReportParams, $user);
    }

    public function run(?WhereItem $where, ?User $user): Result
    {
        $reportData = $this->getDataResults();

        $columns = ['COUNT:id'];
        $groupBy = ['RANGE', 'status'];

        $group1Sums = [];

        $grouping = [[], []];

        foreach ($this->rangeList as $i => $range) {
            $grouping[0][] = $this->getStringRange($i);
        }

        foreach ($reportData as $range => $d1) {
            $group1Sums[$range] = [
                'COUNT:id' => 0
            ];

            foreach ($d1 as $d2) {
                $group1Sums[$range]['COUNT:id'] += $d2['COUNT:id'];
            }
        }

        $statusList = $this->metadata->get('entityDefs.Lead.fields.status.options', []);

        foreach ($statusList as $status) {
            if (!in_array($status, $this->ignoreStatusList)) {
                $grouping[1][] = $status;
            }
        }

        $columnNameMap = [
            'COUNT:id' => $this->language->translate('COUNT', 'functions', 'Report')
        ];

        $groupValueMap = [
            'RANGE' => [],
            'status' => [],
        ];

        foreach ($this->rangeList as $i => $r) {
            $groupValueMap['RANGE'][$this->getStringRange($i)] = $this->getRangeTranslation($i);
        }

        foreach ($grouping[1] as $status) {
            $groupValueMap['status'][$status] = $this->language->translateOption($status, 'status', 'Lead');
        }

        $sums = (object) [];

        $sum = 0;

        foreach ($grouping[0] as $group) {
            if (!isset($group1Sums[$group]) || !isset($group1Sums[$group][$columns[0]])) {
                $group1Sums[$group][$columns[0]] = 0;
            }

            $sum += $group1Sums[$group][$columns[0]];
        }

        $sums->{$columns[0]} = $sum;

        foreach ($reportData as $k => $v) {
            $reportData[$k] = (object) $v;

            foreach ($v as $k1 => $v1) {
                if (is_array($v1)) {
                    $reportData[$k]->$k1 = (object) $v1;
                }
            }
        }

        $reportData = (object) $reportData;

        $result = new Result(
            Lead::ENTITY_TYPE,
            $groupBy,
            $columns,
            $columns,
            $columns,
            [],
            [],
            [],
            null,
            null,
            $sums,
            $groupValueMap,
            $columnNameMap,
            [],
            null,
            $grouping,
            $reportData,
            null,
            null
        );

        $result->setGroup1Sums((object) $group1Sums);
        $result->setGroup1NonSummaryColumnList([]);
        $result->setGroup2NonSummaryColumnList([]);

        return $result;
    }

    private function getStringRange($i): string
    {
        $range = $this->rangeList[$i];

        if (!$range) {
            return '-';
        }

        return $range[0] . '-' .  $range[1];
    }

    private function getRangeTranslation($i)
    {
        $range = $this->rangeList[$i];

        if ($range === null) {
            return $this->language->translate('never', 'labels', 'Report');
        }

        if (empty($range[1])) {
            return '>' . $range[0] . ' ' . $this->language->translate('days', 'labels', 'Report');
        }

        return $range[0] . '-' . $range[1] . ' ' . $this->language->translate('days', 'labels', 'Report');
    }

    /**
     * @param ?array{int, int} $range
     */
    private function getWherePart(?array $range): WherePart
    {
        $completedStatusList1 = $this->metadata->get(['scopes', 'Call', 'completedStatusList']) ?? [];
        $completedStatusList2 = $this->metadata->get(['scopes', 'Meeting', 'completedStatusList']) ?? [];

        $subQueryBuilder1 = SelectBuilder::create()
            ->from(Call::ENTITY_TYPE, 'event')
            ->join('CallLead', 'm',
                C::and(
                    C::equal(
                        E::column('event.id'),
                        E::column('m.callId')
                    ),
                    C::equal(E::column('m.deleted'), false)
                )
            )
            ->where(
                C::equal(
                    E::column('m.leadId'),
                    E::column('lead.id')
                )
            )
            ->where(['status' => $completedStatusList1])
            ->limit(1);

        $subQueryBuilder2 = SelectBuilder::create()
            ->from(Meeting::ENTITY_TYPE, 'event')
            ->join('LeadMeeting', 'm',
                C::and(
                    C::equal(
                        E::column('event.id'),
                        E::column('m.meetingId')
                    ),
                    C::equal(E::column('m.deleted'), false)
                )
            )
            ->where(
                C::equal(
                    E::column('m.leadId'),
                    E::column('lead.id')
                )
            )
            ->where(['status' => $completedStatusList2])
            ->limit(1);

        $subQueryExists1 = SelectBuilder::create()
            ->clone($subQueryBuilder1->build())
            ->select(E::column('id'))
            ->build();

        $subQueryExists2 = SelectBuilder::create()
            ->clone($subQueryBuilder2->build())
            ->select(E::column('id'))
            ->build();

        if (!$range) {
            return C::and(
                C::not(C::exists($subQueryExists1)),
                C::not(C::exists($subQueryExists2))
            );
        }

        $select = E::max(E::column('dateStart'));

        $subQuery1 = $subQueryBuilder1
            ->select($select)
            ->order($select, Order::DESC)
            ->build();

        $subQuery2 = $subQueryBuilder2
            ->select($select)
            ->order($select, Order::DESC)
            ->build();

        if (!$range[1]) {
            $day = DateTime::createNow()
                ->addDays(- $range[0])
                ->getString();

            return C::or(
                C::and(
                    C::exists($subQueryExists1),
                    C::greaterOrEqual(E::value($day), $subQuery1),
                ),
                C::and(
                    C::exists($subQueryExists2),
                    C::greaterOrEqual(E::value($day), $subQuery2),
                )
            );
        }

        $day1 = DateTime::createNow()
            ->addDays(- $range[0])
            ->getString();

        $day2 = DateTime::createNow()
            ->addDays(- $range[1])
            ->getString();

        return C::or(
            C::and(
                C::exists($subQueryExists1),
                C::lessOrEqual(E::value($day2), $subQuery1),
                C::greater(E::value($day1), $subQuery1)
            ),
            C::and(
                C::exists($subQueryExists2),
                C::lessOrEqual(E::value($day2), $subQuery2),
                C::greater(E::value($day1), $subQuery2)
            )
        );
    }

    /**
     * @return array<string, array<string, array<string, int>>>
     */
    private function getDataResults(): array
    {
        $resultData = [];

        foreach ($this->rangeList as $i => $range) {
            $where = $this->getWherePart($range);

            $query = SelectBuilder::create()
                ->from(Lead::ENTITY_TYPE)
                ->select(
                    E::count(E::column('id')),
                    'COUNT:id'
                )
                ->select('status')
                ->where(['status!=' => $this->ignoreStatusList])
                ->where($where)
                ->group('status')
                ->build();

            $sth = $this->entityManager->getQueryExecutor()->execute($query);

            $dateString = $this->getStringRange($i);

            foreach ($sth->fetchAll() as $row) {
                if (!array_key_exists($dateString, $resultData)) {
                    $resultData[$dateString] = [];
                }

                $status = $row['status'];

                $resultData[$dateString][$status] = [
                    'COUNT:id' => (int) $row['COUNT:id'],
                ];
            }
        }

        return $resultData;
    }
}
