<?php


namespace App\Http\Controllers;

use App\Models\Proposal;
use App\Models\Table;
use App\Models\User;
use App\Tables\Changeset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Input;
use MightyPork\Exceptions\SimpleValidationException;
use MightyPork\Utils\Utils;

/**
 * Draft composing
 */
class TableEditController extends Controller
{
    /**
     * Initialize the session-stored changeset, if not set yet
     *
     * @param Table $table
     * @return Changeset
     */
    private function getChangeset(Table $table)
    {
        if (Input::has('reset')) {
            session()->forget($table->draftSessionKey);
        }

        /** @var Changeset $changeset */
        return session()->remember($table->draftSessionKey, function () use ($table) {
            $changeset = new Changeset();
            $changeset->table = $table;
            $changeset->revision = $table->revision;
            return $changeset;
        });
    }

    /**
     * Store the changeset to session
     *
     * @param Changeset $changeset
     */
    private function storeChangeset(Changeset $changeset)
    {
        session()->put($changeset->table->draftSessionKey, $changeset);
    }

    /**
     * Discard draft and redirect to table view
     */
    public function discard(User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");

        session()->forget($tableModel->draftSessionKey);

        return redirect($tableModel->draftRoute);
    }

    #region Draft tabs

    /**
     * Show the table edit view with tabs
     *
     * @param User $user - table owner
     * @param string $table - table name
     * @param string|null $tab - page tab name, kebab-case
     * @return \Illuminate\View\View;
     */
    public function draft(User $user, string $table, $tab = null)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");

        if ($tab == null) $tab = 'edit-rows';

        $method = camel_case('tab-'.$tab);
        if (!method_exists($this, $method)) abort(404, "No such tab: $tab");

        $changeset = $this->getChangeset($tableModel);

        if (Input::has('dump')) {
            dd($changeset);
        }

        return $this->$method($changeset);
    }

    /** @noinspection PhpUnusedPrivateMethodInspection */
    private function tabEditRows(Changeset $changeset)
    {
        $revision = $changeset->revision;
        $columns = $changeset->fetchAndTransformColumns();
        $rows = $revision->rowsData($columns, true, false)
            ->sortByCol($changeset->fetchRevisionColumns()[0])
            ->paginate(25, []);

        return view('table.propose.edit-rows', [
            'changeset' => $changeset,
            'table' => $changeset->table,
            'columns' => collect($columns),
            'rows' => $rows,
        ]);
    }

    /** @noinspection PhpUnusedPrivateMethodInspection */
    private function tabAddRows(Changeset $changeset)
    {
        $columns = $changeset->fetchAndTransformColumns();
        $rows = $changeset->getAddedRows(25);

        return view('table.propose.add-rows', [
            'changeset' => $changeset,
            'table' => $changeset->table,
            'columns' => collect($columns),
            'rows' => $rows,
        ]);
    }

    /** @noinspection PhpUnusedPrivateMethodInspection */
    private function tabAddRowsCsv(Changeset $changeset)
    {
        $columns = $changeset->fetchAndTransformColumns();

        return view('table.propose.add-rows-csv', [
            'changeset' => $changeset,
            'table' => $changeset->table,
            'columns' => collect($columns)->where('toRemove', false),
        ]);
    }

    /** @noinspection PhpUnusedPrivateMethodInspection */
    private function tabManageColumns(Changeset $changeset)
    {
        $columns = $changeset->fetchAndTransformColumns();

        return view('table.propose.manage-columns', [
            'changeset' => $changeset,
            'table' => $changeset->table,
            'columns' => collect($columns),
        ]);
    }

    /** @noinspection PhpUnusedPrivateMethodInspection */
    private function tabReview(Changeset $changeset)
    {
        return view('table.propose.review', [
            'changeset' => $changeset,
            'table' => $changeset->table,
        ]);
    }

    #endregion

    /**
     * API hook called by AJAX or via forms.
     * Generally applies modifications to the Changeset stored in session.
     *
     * @param Request $request
     * @param User $user
     * @param string $table
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function draftUpdate(Request $request, User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");

        $changeset = $this->getChangeset($tableModel);

        $input = objBag($request->all(), false);

        $resp = null;
        $code = 200;

        try {
            switch ($input->action) {
                case 'row.update':
                    $data = (object)$input->data;
                    $resp = $changeset->rowUpdate($data);
                    break;

                case 'row.update-many':
                    $newVals = $input->data;
                    $updated = [];
                    foreach ($newVals as $rowUpdate) {
                        $r = $changeset->rowUpdate($rowUpdate);
                        $updated[$r->_id] = $r;
                    }
                    $resp = $updated;
                    break;

                case 'row.add-csv':
                    $fname = 'csv-file';
                    if ($request->hasFile($fname)) {
                        try {
                            $file = $request->file($fname);
                            if ($file->isValid() && $file->isReadable()) {
                                $handle = $file->openFile();
                                $csv = Utils::csvToArray($handle);
                                if (empty($csv)) throw new \Exception("Failed to parse CSV file.");

                                $changeset->addFilledRows($csv);
                                $handle = null;
                            } else {
                                throw new \Exception("File not valid.");
                            }
                        } catch (\Exception $e) {
                            return $this->backWithErrors(['csv-file' => $e->getMessage()]);
                        }
                    }
                    else {
                        try {
                            $text = trim($input->data);
                            if (empty($text)) throw new \Exception("Empty CSV field and no file uploaded.");
                            $changeset->addFilledRows(Utils::csvToArray($text));
                        } catch (\Exception $e) {
                            return $this->backWithErrors(['data' => $e->getMessage()]);
                        }
                    }
                    break;

                case 'row.remove':
                    $isNew = $changeset->isNewRow($input->id);
                    $changeset->rowRemove($input->id);
                    $resp = $isNew ? null : $changeset->fetchAndTransformRow($input->id);
                    break;

                case 'row.remove-empty-new':
                    $changeset->removeEmptyNewRows();
                    break;

                case 'row.restore':
                    $changeset->rowRestore($input->id);
                    $resp = $changeset->fetchAndTransformRow($input->id);
                    break;

                case 'rows.add':
                    $changeset->addBlankRows($input->count);
                    break;

                case 'col.update':
                    $data = (object)$input->data;
                    $resp = $changeset->columnUpdate($data);
                    break;

                case 'col.remove':
                    $isNew = $changeset->isNewColumn($input->id);
                    $changeset->columnRemove($input->id);
                    $resp = $isNew ? null : $changeset->fetchAndTransformColumn($input->id);
                    break;

                case 'col.restore':
                    $changeset->columnRestore($input->id);
                    $resp = $changeset->fetchAndTransformColumn($input->id);
                    break;

                case 'col.add':
                    $resp = $changeset->addBlankCol();
                    break;

                case 'col.sort':
                    $changeset->setColOrder($input->order);
                    $resp = !empty($changeset->columnOrder); // return flag if order is changed
                    break;

                case 'note.set':
                    $changeset->note = $input->text;
                    break;

                case 'reset.col-order': // called via POST or GET
                    $changeset->resetColumnOrder();
                    $resp = $changeset->fetchAndTransformColumns(); // return all columns
                    break;

                case 'reset.col-remove': // called via GET
                    $changeset->resetRemovedColumns();
                    break;

                case 'reset.col-add': // called via GET
                    $changeset->resetAddedColumns();
                    break;

                case 'reset.col-update': // called via GET
                    $changeset->resetUpdatedColumns();
                    break;

                case 'reset.row-remove': // called via GET
                    $changeset->resetRemovedRows();
                    break;

                case 'reset.row-add': // called via GET
                    $changeset->resetAddedRows();
                    break;

                case 'reset.row-update': // called via GET
                    $changeset->resetUpdatedRows();
                    break;

                default:
                    $resp = "Bad Action";
                    $code = 400;
                    break;
            }
        } catch (SimpleValidationException $e) {
            return $this->jsonResponse(['errors' => $e->getMessageBag()->getMessages()], 400);
        }

        $this->storeChangeset($changeset);

        if ($request->method() == 'GET') {
            return back();
        }

        // Redirect requested via form
        if ($code == 200 && $input->has('redirect')) {
            return redirect($input->redirect);
        }

        return $this->jsonResponse($resp, $code);
    }

    /**
     * Submit the form
     *
     * @param Request $request
     * @param User $user
     * @param string $table
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function draftSubmit(Request $request, User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        if ($tableModel === null) abort(404, "No such table.");

        $input = $this->validate($request, [
            'note' => 'required|string'
        ]);

        $changeset = $this->getChangeset($tableModel);
        $changeset->note = trim($input->note);
        if (empty($changeset->note)) throw new SimpleValidationException('note', 'Note is required.');

        if (!$changeset->getRowChangeCounts()->any && !$changeset->getColumnChangeCounts()->any) {
            flash()->error("There are no changes to submit.");
            return back();
        }

        $proposal = Proposal::fromChangeset($changeset);
        $proposal->saveOrFail();

        if (\user()->ownsTable($tableModel)) {
            $proposal->createRevision();
        } else {
            // TODO send a notification to the table owner
        }

        session()->forget($tableModel->draftSessionKey);

        return redirect($tableModel->viewRoute);
    }
}