<?php

namespace App\Http\Controllers;

use App\Models\Revision;
use App\Models\Table;
use App\Models\User;
use App\Tables\Changeset;
use App\Tables\Column;
use App\Tables\ColumnNumerator;
use App\Tables\CStructArrayExporter;
use App\Tables\CsvExporter;
use App\Tables\CXMacroExporter;
use App\Tables\JsonExporter;
use App\Tables\PhpExporter;
use App\Tables\RowNumerator;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use MightyPork\Exceptions\NotApplicableException;
use MightyPork\Utils\Utils;

class TableController extends Controller
{
    public function view(Request $request, User $user, string $table)
    {
        $input = $this->validate($request, [
            'rev' => 'nullable|int'
        ]);

        /** @var Table $tableModel */
        $tableModel = $user->tables()->withCount(['favourites', 'forks', 'revisions', 'comments'])
            ->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");

        // option to show other revisions
        if ($input->has('rev')) {
            $rev = (int)$input->rev;
            $revision = $tableModel->revisions()->orderBy('created_at')->skip($rev - 1)->first();
            if ($revision === null) abort(404, "No such revision");
        } else {
            $revision = $tableModel->revision;
        }

        $this->countTableVisit($request, $tableModel);

        $columns = Column::columnsFromJson($revision->columns);

        $rows = $revision->rowsData($columns)->paginate(25, []);

        return view('table.view', [
            'table' => $tableModel,
            'revision' => $revision,
            'proposals_count' => $tableModel->proposals()->unmerged($tableModel)->count(),
            'columns' => $columns,
            'rows' => $rows,
        ]);
    }

    public function delete(Request $request, User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");
        $this->authorize('delete', $tableModel);

        if ($request->get('table-name', null) !== $table) {
            return $this->backWithErrors(['table-name' => 'Fill table name']);
        }

        $tableModel->delete();

        return redirect(route('profile.view', $user->name));
    }

    /**
     * SHow a form for creating a new table
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $exampleData =
            "Mercenaria mercenaria,hard clam,40\n" .
            "Magallana gigas,pacific oyster,30\n" .
            "Patella vulgata,common limpet,20";

        $columns = Column::columnsFromJson([
            // fake 'id' to satisfy the check in Column constructor
            ['id' => 1, 'name' => 'latin',    'type' => 'string', 'title' => 'Latin Name'],
            ['id' => 2, 'name' => 'common',   'type' => 'string', 'title' => 'Common Name'],
            ['id' => 3, 'name' => 'lifespan', 'type' => 'int',    'title' => 'Lifespan (years)']
        ]);

        return view('table.create', [
            'exampleColumns' => '',
            'columns' => $columns,
            'exampleData' => $exampleData,
        ]);
    }

    /**
     * Show table settings edit form
     */
    public function settings(Request $request, User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");
        $this->authorize('edit', $tableModel);

        return view('table.conf', [
            'table' => $tableModel,
        ]);
    }

    /**
     * Store modified table settings
     */
    public function storeSettings(Request $request, User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");
        $this->authorize('edit', $tableModel);

        $input = $this->validate($request, [
            'name' => [
                'required',
                VALI_NAME,
                Rule::unique('tables')->ignoreModel($tableModel),
            ],
            'title' => ['required', VALI_LINE],
            'description' => ['nullable', VALI_TEXT],
            'license' => ['nullable', VALI_TEXT],
            'origin' => ['nullable', VALI_TEXT],
        ]);

        $tableModel->fill($input->all());
        $tableModel->save();

        flash()->success('Table settings saved');

        return redirect($tableModel->viewRoute); // the route now changed
    }

    public function storeNew(Request $request)
    {
        /** @var User $u */
        $u = \Auth::user();

        $input = $this->validate($request, [
            'name' => [
                'required',
                VALI_NAME,
                Rule::unique('tables'),
            ],
            'title' => ['required', VALI_LINE],
            'description' => ['nullable', VALI_TEXT],
            'license' => ['nullable', VALI_TEXT],
            'origin' => ['nullable', VALI_TEXT],
            'columns' => 'required|json',
            'data' => 'string|nullable',
        ]);

        // Check if table name is unique for user
        if ($u->hasTable($input->name)) {
            return $this->backWithErrors([
                'name' => "A table called \"$input->name\" already exists in your account.",
            ]);
        }

        // --- COLUMNS ---
        // Parse and validate the columns specification
        /** @var Column[] $columns */
        $columns = [];
        $col_names = []; // for checking duplicates
        $colsArray = fromJSON($input->columns);

        // prevent griefing via long list of columns
        if (count($colsArray) > 100) return $this->backWithErrors(['columns' => "Too many columns"]);

        foreach ($colsArray as $colObj) {
            // ensure column has a title
            if (!isset($colObj->title)) {
                $colObj->title = $colObj->name;
            }

            try {
                if (in_array($colObj->name, $col_names)) {
                    return $this->backWithErrors(['columns' => "Duplicate column: $colObj->name"]);
                }

                $col_names[] = $colObj->name;
                $columns[] = new Column($colObj);
            } catch (\Exception $e) {
                // validation errors from the Column constructor
                return $this->backWithErrors(['columns' => $e->getMessage()]);
            }
        }
        if (count($columns) == 0) {
            return $this->backWithErrors(['columns' => "Define at least one column"]);
        }

        // Now assign column IDs
        $columnNumerator = new ColumnNumerator(count($columns));
        foreach ($columns as $column) {
            $column->setID($columnNumerator->next());
        }

        // --- DATA ---
        $dataCsvLines = Utils::csvToArray($input->data);

        // Preparing data to insert into the Rows table
        $rowsToInsert = null;
        $rowNumerator = null;
        try {
            $rowsToInsert = (new Changeset)->csvToRowsArray($columns, $dataCsvLines, true, false)->all();
        } catch (\Exception $e) {
            return $this->backWithErrors(['data' => $e->getMessage()]);
        }

        // --- STORE TO DB ---

        /* Fields for the new Revision instance */
        $revisionFields = [
            'note' => "Initial revision of table $u->name/$input->name",
            'columns' => $columns,
            'row_count' => count($rowsToInsert),
        ];

        /* Fields for the new Table instance */
        $tableFields = [
            'owner_id' => $u->id,
            'name' => $input->name,
            'title' => $input->title,
            'description' => $input->description,
            'license' => $input->license,
            'origin' => $input->origin,
        ];

        /** @var Table $table */
        $table = null;
        \DB::transaction(function () use ($revisionFields, $tableFields, $rowsToInsert, &$table) {
            $revision = Revision::create($revisionFields);

            $tableFields['revision_id'] = $revision->id; // current revision (not-null constraint on this FK)
            $table = Table::create($tableFields);

            // Attach the revision to the table, set as current
            $table->revisions()->attach($revision);
            $table->revision()->associate($revision);

            // Spawn rows, linked to the revision
            $revision->rows()->createMany($rowsToInsert);
        });

        return redirect($table->viewRoute);
    }

    /**
     * Check unique visit, filter bots / scripts, and increment visits count.
     *
     * @param Request $request
     * @param Table $table
     */
    private function countTableVisit(Request $request, Table $table)
    {
        $cookieName = "view_{$table->owner->name}_{$table->name}";
        if (!$request->cookie($cookieName, false)) {
            $ua = $request->userAgent();
            // Filter out suspicious user agents
            if (! str_contains(strtolower($ua), Controller::BOT_USER_AGENTS)) {
                $table->countVisit();
                \Cookie::queue($cookieName, true, 24*60); // in minutes
            }
        }
    }

    /**
     * Simple export via a preset
     *
     * @param Request $request
     * @param User $user
     * @param string $table
     */
    public function export(Request $request, User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");

        $exporter = null;

        switch ($request->get('format')) {
            case 'json':
                $exporter = new JsonExporter($tableModel);
                break;

            case 'csv':
                $exporter = new CsvExporter($tableModel);
                break;

            case 'csv-tab':
                $exporter = (new CsvExporter($tableModel))->withDelimiter("\t");
                break;

            case 'c':
                $exporter = new CStructArrayExporter($tableModel);
                break;

            case 'xmacro':
                $noq = explode(',', $request->input('noq',''));
                $exporter = (new CXMacroExporter($tableModel))->noQuotesAround($noq);
                break;

            case 'php':
                $exporter = new PhpExporter($tableModel);
                break;

            case 'js':
                $exporter = (new JsonExporter($tableModel))->withJsWrapper();
                break;

            default:
                abort(400, "Unspecified or unknown format.");
        }

        $dl = Utils::parseBool($request->get('dl', false));
        $exporter->exportToBrowser($dl);
    }
}