<?php

namespace Ignite\Core\Models\Dashboard\Charts;

use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Ignite\Core\Contracts\Dashboard\WithInlineSeries;
use Ignite\Core\Contracts\Dashboard\WithPercentageChange;
use Ignite\Core\Entities\Filters\QueryPermissionFilters;
use Ignite\Core\Entities\Participant;
use Ignite\Core\Entities\User;
use Ignite\Core\Models\Dashboard\Concerns\DeterminesPercentageChange;
use Ignite\Core\Models\Dashboard\Stat;
use Illuminate\Support\Facades\DB;

class ParticipantsEnrolledStat extends Stat implements WithPercentageChange, WithInlineSeries
{
    use DeterminesPercentageChange;

    /**
     * Create a new stat instance.
     *
     * @param array|object $attributes
     */
    public function __construct($attributes = [])
    {
        $attributes = array_merge([
            'label' => 'Participants Enrolled',
            'color' => 'blue',
            'type' => 'metric',
            'link' => url('/admin/participant/participants'),
            'sort' => 3,
        ], $attributes);

        parent::__construct($attributes);
    }

    /**
     * The HTML element ID.
     *
     * @return string
     */
    public function getElement()
    {
        return 'participants-enrolled-correlated-stat';
    }

    /**
     * The chart size in the layout.
     *
     * @return string
     */
    public function getSize()
    {
        return $this->get('size', 'col-xs-6 col-sm-3');
    }

    /**
     * Get the correct data value from storage.
     *
     * @return mixed
     */
    public function getData()
    {
        if (! $this->get('data', false)) {
            $result = QueryPermissionFilters::for('core.user.participant.browse')->apply(
                DB::table('core_participant')
                    ->select(DB::raw('COUNT(user_id) as total'))
                    ->selectSub($this->getSubQuery($this->getDateRange()), 'current')
                    ->selectSub($this->getSubQuery($this->getPreviousDateRange()), 'previous')
            )->first();

            if ($this instanceof WithPercentageChange) {
                $this->setPercentageChange($result->current, $result->previous);
            }

            $this->offsetSet('data', $result->current);
        }

        if ($this instanceof WithInlineSeries) {
            $this->setSeries($this->getSeriesData());
        }

        return $this->get('data');
    }

    /**
     * Query the database for series data.
     *
     * @return array
     */
    protected function getSeriesData()
    {
        $results = $this->buildRangedQuery($this->getDateRange())->get();

        return $results->keyBy('date')->map->total->toArray();
    }

    /**
     * @return \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|\Illuminate\Foundation\Application|mixed
     */
    protected function getParticipantTypes()
    {
        return config('core.report.participant-types', resolve(Participant::class)->distinct()->get('type'));
    }

    /**
     * The query scoped to the given range.
     * @param array $range
     *
     * @return \Illuminate\Database\Query\Builder
     */
    protected function buildRangedQuery($range)
    {
        return QueryPermissionFilters::for('core.user.participant.browse')->apply(
            DB::table('core_participant')
                ->selectRaw('DATE(core_participant.created_at) as `date`, COUNT(core_participant.user_id) as `total`')
                ->join('core_user', 'core_user.user_id', '=', 'core_participant.user_id')
                ->whereNull('core_participant.deleted_at')
                ->where('core_participant.internal', User::TYPE_PARTICIPANT)
                ->where('core_participant.status', '=', User::STAT_ACTIVE)
                ->whereIn('core_participant.type', $this->getParticipantTypes())
                ->groupBy('date')
                ->havingBetween('date', $range)
                ->orderBy('date', 'asc')
        );
    }

    /**
     * Get the sub-query with date constraints.
     *
     * @param array $range
     *
     * @return \Illuminate\Database\Query\Builder
     */
    protected function getSubQuery(array $range)
    {
        /**
         * @var Carbon $end
         * @var Carbon $end
         */
        list($start, $end) = $range;

        return QueryPermissionFilters::for('core.user.participant.browse')->apply(
            DB::table('core_participant')
                ->selectRaw('COUNT(user_id)')
                ->where('internal', User::TYPE_PARTICIPANT)
                ->where('status', '=', User::STAT_ACTIVE)
                ->whereIn('core_participant.type', $this->getParticipantTypes())
                ->whereNull('deleted_at')
                ->whereRaw(
                    'DATE(created_at) BETWEEN ? AND ?',
                    [$start->format('Y-m-d'), $end->format('Y-m-d')]
                )
        );
    }

    /**
     * Set the percentage change values using current versus previous ranges.
     *
     * @param int|float $current
     * @param int|float $previous
     *
     * @return self
     */
    public function setPercentageChange($current, $previous)
    {
        $this->offsetSet('current', number_format($current, 0));
        $this->offsetSet('previous', number_format($previous, 0));
        $this->offsetSet('direction', $this->determineChangeType($current, $previous));
        $this->offsetSet('percentage', $this->determinePercentageChange($current, $previous));

        return $this;
    }

    public function setSeries(array $data)
    {
        $values = [];
        $type = 'range';

        foreach ($data as $date => $value) {
            if ($type === 'month') {
                $key = Carbon::parse($date)->format('d');
            } else {
                $key = $date;
            }
            $values[$key] = intval($value);
        }

        /**
         * @var Carbon $start
         * @var Carbon $end
         */
        list($start, $end) = $this->getDateRange();
        $intervals = CarbonPeriod::create($start, $end);

        foreach ($intervals as $interval) {
            if ($type === 'month') {
                $key = $interval->format('d');
            } else {
                $key = $interval->format('Y-m-d');
            }

            if (! array_key_exists($key, $values)) {
                $values[$key] = 0;
            }
        }

        ksort($values);

        $this->offsetSet('series', $values);
    }
}
