<?php

namespace App\Builders;

use App\Models\Resource;
use Illuminate\Support\Carbon;
use App\Services\DataConnectionService;
use App\Builders\Helpers\PaginationHelper;
use App\Services\DataConnectionTablesService;

class ChartDrillBuilder
{
    private $chart;
    private $xValue;
    private $connection;
    private $query;
    private $options;
    private $dataConnectionService;

    public function __construct(Resource $chart, $xValue = null)
    {
        $this->chart                 = $chart;
        $this->xValue                = $xValue;
        $this->dataConnectionService = new DataConnectionService();
    }

    private function initializeConnection()
    {
        $this->query = (new DataConnectionService())
            ->getConnection(
                isset($this->chart->data_connection_id)
                ? $this->chart->data_connection_id
                : 'default'
            );

        return $this;

    }

    public function populateOptions()
    {
        $this->options = [
            'name'                => $this->chart->getResourceConfiguration('name'),
            'title'               => $this->chart->getResourceConfiguration('title'),
            'chart_type'          => $this->chart->getResourceConfiguration('chart_type'),
            'x_table'             => $this->chart->getResourceConfiguration('x_table'),
            'y_table'             => $this->chart->getResourceConfiguration('y_table'),
            'x_axis_column'       => $this->chart->getResourceConfiguration('x_axis_column'),
            'y_axis_column'       => $this->chart->getResourceConfiguration('y_axis_column'),
            'data_filter_columns' => $this->chart->getResourceConfiguration('data_filter_columns'),
            'date_range'          => $this->chart->getResourceConfiguration('date_range'),
            'date_time_scale'     => $this->chart->getResourceConfiguration('date_time_scale'),
            'function'            => $this->chart->getResourceConfiguration('function'),
            'drill_down'          => $this->chart->getResourceConfiguration('drill_down'),
            'drill_down_columns'  => $this->chart->getResourceConfiguration('drill_down_columns'),
            'label'               => $this->chart->getResourceConfiguration('label')

        ];

        return $this;
    }

    private function addTables()
    {

        if ($this->options['x_table'] == $this->options['y_table']) {

            $this->query = $this->query->table(
                $this->options['x_table']
            );

        } else {

            $relation = explode("=", getTablesRelation($this->query, $this->options['x_table'], $this->options['y_table']));

            $this->query = $this->query
                ->table(
                    $this->options['x_table']
                )->join(
                $this->options['y_table'],
                $relation[0],
                "=",
                $relation[1]
            );
        }

        return $this;
    }

    private function addAxis()
    {

        if (!$this->isXAxisDateTime()) {
            $this->query = $this->query->selectRaw(
                $this->getSelect()
            );
        }

        return $this;
    }

    private function getXAxis($backTick = null)
    {

        if ($backTick) {
            return "`{$this->options['x_table']}`.`{$this->options['x_axis_column']}`";

        }

        return "{$this->options['x_table']}.{$this->options['x_axis_column']}";
    }

    private function getSelect()
    {
        return implode(",", $this->getDrillColumns());
    }

    private function getDrillColumns()
    {
        $drillColumns = [];

        $drillColumns = array_map(function ($column) {
            return "`" . $this->options['y_table'] . "`" . '.' . "`" . $column . "`";
        }, $this->options['drill_down_columns']);
        array_unshift($drillColumns, $this->getXAxis(true));
        return array_unique($drillColumns);

    }

    private function getDrillColumnsWithoutTable()
    {
        $drillColumns = [];

        $drillColumns = array_map(function ($column) {
            return $column;
        }, $this->options['drill_down_columns']);

        array_unshift($drillColumns, $this->options['x_axis_column']);
        return array_unique($drillColumns);

    }

    private function addDateFilters()
    {

        if ($this->isXAxisDateTime()) {
            $this->query = $this->query->whereBetween(
                $this->getXAxis(),
                $this->getDatesBasedOnTimeScale()
            )->selectRaw(
                $this->getSelect()
            );
        }

        return $this;
    }

    private function isXAxisDateTime()
    {
        return (
            $this->options['chart_type'] == "timeseries"
            || in_array(
                $this->getColumnType($this->options['x_table'], $this->options['x_axis_column']),
                ["timestamp", "date", "datetime"]
            )
        );
    }

    private function isXAxisTextual()
    {

        return (
            $this->options['chart_type'] != "timeseries"
            && !in_array(
                $this->getColumnType($this->options['x_table'], $this->options['x_axis_column']),
                ["timestamp", "date", "datetime"]
            )

        );

    }

    private function getColumnType($table, $column)
    {
        $connection = isset($this->chart->data_connection_id)
        ? $this->chart->data_connection_id
        : 'default';

        return (new DataConnectionTablesService($this->dataConnectionService))->getColumnType(
            $connection,
            $table,
            $column
        );

    }

    private function addWhereFilters()
    {

        if ($this->isXAxisTextual()) {

            if (!is_null($this->xValue)) {
                $this->query = $this->query->where(
                    $this->getXAxis(),
                    $this->xValue
                );
            }

        }

        return $this;
    }

    private function addOrderBy()
    {
        $this->query = $this->query->orderByRaw($this->getXAxis());

        return $this;
    }

    private function getDatesBasedOnTimeScale()
    {

        if (!empty($this->options['date_time_scale']) && !is_null($this->xValue)) {
            $xValue = Carbon::parse(explode("to", $this->xValue)[0]);

            switch ($this->options['date_time_scale']) {
                case 'years':
                    return [$xValue->toDateTimeString(), $xValue->endOfYear()->toDateTimeString()];
                    break;
                case 'quarters':
                    return [$xValue->toDateTimeString(), $xValue->addMonths(2)->endOfMonth()->toDateTimeString()];
                    break;
                case 'months':
                    return [$xValue->toDateTimeString(), $xValue->endOfMonth()->toDateTimeString()];
                    break;
                case 'weeks':
                    return $this->handleWeeks($xValue);
                    break;
                case 'days':
                    return [$xValue->startOfDay()->toDateTimeString(), $xValue->endOfDay()->toDateTimeString()];
                    break;
                case 'hours':
                    return [$xValue->toDateTimeString(), $xValue->endOfHour()->toDateTimeString()];
                    break;
                default:
                    break;
            }

        }

    }

    public function handleWeeks($xValue)
    {

        switch ($this->options['date_range']) {
            case 'this_month':
                $newDate = $xValue->copy()->addDays(6);

                if ($newDate->month > $xValue->month) {

                    return [$xValue->toDateTimeString(), $xValue->endOfMonth()->toDateTimeString()];
                } else {
                    return [$xValue->toDateTimeString(), $xValue->addDays(6)->endOfDay()->toDateTimeString()];
                }

                break;
            case 'last_30_days':
                $newDate = $xValue->copy()->addDays(6);

                if ($newDate > now()->subDay()) {

                    return [$xValue->toDateTimeString(), now()->subDay()->endOfDay()->toDateTimeString()];
                } else {
                    return [$xValue->toDateTimeString(), $xValue->addDays(6)->endOfDay()->toDateTimeString()];
                }

                break;

            default:
                break;
        }

// if($this->options['date_range'] == "this_month") {

//     $newDate = $xValue->copy()->addDays(6);

//     if($newDate->month > $xValue->month) {

//         return [$xValue->toDateString(), $xValue->endOfMonth()->toDateString()];

//     } else {

//         return [$xValue->toDateString(), $xValue->addDays(6)->endOfDay()->toDateString()];

//     }

// } elseif($this->options['date_range'] == "last_30_days") {

//     $newDate = $xValue->copy()->addDays(6);

//     if($newDate > now()->subDay()) {

//         return [$xValue->toDateString(), now()->subDay()->endOfDay()->toDateString()];

//     } else {

//         return [$xValue->toDateString(), $xValue->addDays(6)->endOfDay()->toDateString()];

//     }
        // }

    }

    private function prepareData($data)
    {
        $xAxis = $this->options['x_axis_column'];

        $drillMaxRecords = config('srm_config.dashboard.drill_down_max_records_per_page', 25);

        $sortedCollection = $data->map(function ($item) use ($xAxis) {
            ${$xAxis}

            = $item->$xAxis;
            unset($item->${$xAxis});
            return (object) array_merge(["$xAxis" => ${$xAxis}

            ], (array) $item);
        })->values();

        $keys = collect($this->getDrillColumnsWithoutTable());

        $label = $this->getDatesBasedOnTimeScale();

        return [PaginationHelper::paginate($sortedCollection, $drillMaxRecords), $keys, $label];

    }

    public function build()
    {
        $this->initializeConnection()
            ->populateOptions()
            ->addTables()
            ->addAxis()
            ->addDateFilters()
            ->addWhereFilters()
            ->addOrderBy();
        return $this->prepareData($this->query->get());
    }

}
