<?php

namespace Ignite\Core\Models\Grid;

use Ignite\Core\Contracts\Table;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Connection;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Enumerable;
use Yajra\DataTables\DataTableAbstract;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Html\Builder as HtmlBuilder;
use Yajra\DataTables\Services\DataTable;

abstract class AbstractTable extends DataTable implements Table
{
    /**
     * Flag to use fast-excel package for export.
     *
     * From what I've seen, this does not support fetching column values from Eloquent relationships. Be careful
     * turning this on if your table is not fetching column values directly from the query's select clause.
     * (column formatters are not affected by this limitation).
     *
     * @var bool
     */
    protected bool $fastExcel = false;

    /**
     * Flag to enable/disable fast-excel callback.
     * Note: Disabling this flag can improve you export time.
     * Enabled by default to emulate the same output with laravel-excel.
     *
     * It looks like when this is off, FastExcel dumps all columns from the select expression into the export.
     * So, it ignores the dynamic column visibility setting and the columns we define via the columns method.
     *
     * @var bool
     */
    protected bool $fastExcelCallback = true;

    /**
     * The datatables factory instance.
     *
     * @var DataTables
     */
    protected DataTables $datatables;

    /**
     * The database manager instance.
     *
     * @var DatabaseManager
     */
    protected DatabaseManager $database;

    /**
     * The dynamic parameters to apply to the report, if any.
     *
     * @var array
     */
    protected array $params;

    /**
     * Export class handler.
     *
     * @var string
     */
    protected string $exportClass = DataTablesExportHandler::class;

    /**
     * TaxReport constructor.
     *
     * @param DataTables $datatables
     * @param DatabaseManager $databaseManager
     * @param array $params
     */
    public function __construct(DataTables $datatables, DatabaseManager $databaseManager, array $params = [])
    {
        $this->datatables = $datatables;
        $this->database = $databaseManager;
        $this->params = $params;
    }

    /**
     * Get the database connection for a query engine report.
     *
     * @return Connection
     */
    protected function getConnection(): Connection
    {
        return $this->database->connection();
    }

    /**
     * Create a raw query value.
     *
     * @param $value
     *
     * @return Expression
     */
    protected function raw($value): Expression
    {
        return $this->database->connection()->raw($value);
    }

    /**
     * The columns to show.
     * @see \Yajra\DataTables\Html\Column::__construct
     *
     * @return array
     */
    abstract public function columns(): array;

    /**
     * The table query.
     *
     * @return EloquentBuilder|QueryBuilder|\Illuminate\Support\Collection
     */
    abstract public function query(): EloquentBuilder|Collection|QueryBuilder;

    /**
     * Attempt to merge any form label information if form information exists.
     *
     * @return array
     */
    protected function getColumns(): array
    {
        // Get the columns for the report instance.
        return $this->columns();
    }

    /**
     * Gives you the names of the columns in this table.
     *
     * @param Enumerable|array|string|null $except
     * @return Collection
     */
    protected function getColumnNames($except = null): Collection
    {
        $except = ($except instanceof Enumerable ? $except->all() : Arr::wrap($except));

        return collect($this->getColumns())
            ->except($except)
            ->pluck('name')
            ->except($except);
    }

    /**
     * Render the table as HTML.
     *
     * @return HtmlBuilder
     */
    public function html(): HtmlBuilder
    {
        return parent::html()
            ->columns($this->getColumns())
            ->ajax([
                'method' => 'POST',
                'headers' => [
                    'X-CSRF-TOKEN' => csrf_token(),
                ],
            ])
            ->parameters($this->getBuilderParameters());
    }

    /**
     * Get the configuration for the default column visibility button.
     *
     * @return array
     */
    protected function getColumnVisibilityButton(): array
    {
        return [
            'extend' => 'colvis',
            'columns' => array_map(
                function ($column) {
                    return $column.':name';
                },
                collect($this->getColumns())->whereNotNull('name')->whereNotNull('title')
                    ->keyBy('name')->keys()->toArray()
            ),
        ];
    }

    /**
     * Get default builder parameters.
     *
     * @param array $params
     *
     * @return array
     */
    protected function getBuilderParameters(): array
    {
        $params = func_get_args();
        $parentParams = parent::getBuilderParameters();
        $newParams = [
            'stateSave' => true,
            'responsive' => true,
            'filter' => true,
            'lengthMenu' => $this->params['length'] ?? [25, 50, 100, 250],
            'order' => [[0, 'desc']],
            'dom' => '<"#dt-header.clearfix"Bf>r<".dt-wrapper"t><".dt-footer"ip>',
            'ajax' => ['method' => 'POST'],
            'buttons' => $this->getBuilderButtons($parentParams['buttons'] ?? []),
        ];

        return array_merge($parentParams, $newParams, ...$params);
    }

    /**
     * Get default builder buttons.
     *
     * @param  array $buttons
     * @return array
     */
    protected function getBuilderButtons(array $buttons = []): array
    {
        $buttons = array_merge(
            [$this->getColumnVisibilityButton()],
            $buttons
        );
        $newButtons = [
            'pageLength',
            // 'postCsv',
            // 'postExcel',
            // 'print',
        ];
        foreach ($newButtons as $item) {
            if (!in_array($item, $buttons)) {
                $buttons[] = $item;
            }
        }

        return $buttons;
    }

    /**
     * The column definitions for selectable row checkboxes.
     *
     * @param int $target
     * @param string $selector
     *
     * @return array
     */
    protected function getCheckboxColumnParameters($target = 0, $selector = 'td:first-child'): array
    {
        return [
            'select' => [
                'info' => true,
                'style' => 'multi',
                'selector' => $selector,
            ],
            'columnDefs' => [
                [
                    'targets' => $target,
                    'checkboxes' => [
                        'selectRow' => true,
                    ],
                ],
            ],
        ];
    }

    /**
     * Apply query scopes.
     *
     * @param  QueryBuilder|EloquentBuilder  $query
     * @return EloquentBuilder|Relation|QueryBuilder
     */
    protected function applyScopes($query): EloquentBuilder|Relation|QueryBuilder
    {
        foreach ($this->scopes as $scope) {
            $scope->apply($query);
        }

        return $query;
    }

    /**
     * Obscure sensitive data fields.
     * Alias for initDataTable()
     *
     * @param DataTableAbstract $table
     * @return DataTableAbstract
     * @deprecated
     */
    protected function _dataTable(DataTableAbstract $table): DataTableAbstract
    {
        return $this->initDataTable($table);
    }

    /**
     * Obscure sensitive data fields.
     *
     * @param DataTableAbstract $table
     * @return DataTableAbstract
     */
    protected function initDataTable(DataTableAbstract $table): DataTableAbstract
    {
        $columns = $this->getColumns();
        $sensitive = collect($columns)->where('sensitive', true)->keys();
        $allowDecrypt = property_exists($this, 'allowDecrypt') && $this->allowDecrypt === true;

        foreach ($sensitive as $key) {
            $table->editColumn($key, function ($column) use ($key, $allowDecrypt) {
                $value = trim($column->$key);

                if (empty($value)) {
                    return '';
                }

                try {
                    $value = decrypt($value);
                    if ($allowDecrypt) {
                        return $value;
                    }
                    return str_repeat('*', strlen($value));
                } catch (DecryptException $e) {
                    return str_repeat('x', 8);
                }
            });
        }

        if (method_exists($this, 'getColumnFormattingMap')) {
            $specials = $this->getColumnFormattingMap();

            foreach ($specials as $name => $content) {
                if (array_key_exists($name, $this->getColumns())) {
                    $table->editColumn($name, $content);
                }
            }
        }

        return $table;
    }

    /**
     * Create the basic configuration for an actions column in Ignite.
     *
     * @return array
     */
    protected function actionsColumn(): array
    {
        return [
            'name' => 'actions',
            'title' => 'Actions',
            'searchable' => false,
            'orderable' => false,
            'exportable' => false,
            'printable' => false,
            'visible' => true,
            'width' => '80px',
            'class' => 'actions',
        ];
    }

    /**
     * Create the basic configuration for a checkbox column in Ignite.
     *
     * @return array
     */
    protected function checkboxColumn(): array
    {
        return [
            'defaultContent' => '',
            'title' => '&nbsp;',
            'name' => 'checkbox',
            'targets' => 0,
            'sensitive' => false,
            'orderable' => false,
            'searchable' => false,
            'exportable' => false,
            'printable' => true,
            'width' => '10px',
            'order' => 1,
        ];
    }

    /**
     * Create the basic configuration for a deleted_at column in Ignite.
     *
     * @return array
     */
    protected function deletedAtColumn(): array
    {
        return [
            'defaultContent' => '',
            'title' => 'Deleted At',
            'name' => 'claim_participant.deleted_at',
            'targets' => 0,
            'sensitive' => false,
            'orderable' => false,
            'searchable' => true,
            'exportable' => true,
            'printable' => true,
            'visible' => true,
        ];
    }

    /**
     * Replace a column configuration.
     *
     * @param array $columns
     * @param array $replace
     * @param string $column
     * @return array
     */
    protected function replaceColumn(array $columns, array $replace, $column): array
    {
        $offset = array_search($column, array_keys($columns));

        unset($columns[$column]);

        return array_slice($columns, 0, $offset, true) +
            $replace +
            array_slice($columns, $offset, null, true);
    }

    /**
     * Export results to Excel and store in a filesystem.
     *
     * @param string $filepath
     * @param string $disk
     * @param string $extension
     * @return bool|\Illuminate\Foundation\Bus\PendingDispatch
     */
    public function storeAsExcel($filepath = '', $disk = 'local', $extension = '')
    {
        $extension = empty($extension) ? strtolower($this->excelWriter) : $extension;
        if (empty($filepath)) {
            $filepath = $this->getFilename().'.'.ltrim($extension, '.');
        }

        return $this->buildExcelFile()->store($filepath, $disk, $this->excelWriter);
    }

    /**
     * Little fix here for the fast excel callback.
     * Might be something we could contribute to yajra data tables, or have them tell us there's a better way :).
     * See: https://flareapp.io/occurrence/73536484#F7
     *
     * @return \Closure
     */
    public function fastExcelCallback(): \Closure
    {
        return function ($row) {
            return parent::fastExcelCallback()($row instanceof \stdClass ? (array)$row : $row);
        };
    }
}
