<?php

namespace App\Http\Controllers;

use App\Models\Revision;
use App\Models\Row;
use App\Models\Table;
use App\Models\User;
use App\Utils\Column;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use MightyPork\Exceptions\NotApplicableException;

class TableController extends Controller
{
    public function view(User $user, string $table)
    {
        /** @var Table $tableModel */
        $tableModel = $user->tables()->where('name', $table)->first();
        $revision = $tableModel->revision;

        return view('table.view', [
            'table' => $tableModel,
            'revision' => $revision,
            'columns' => Column::columnsFromJson(json_decode($revision->columns)),
            'rows' => $revision->rows,
        ]);
    }

    /**
     * SHow a form for creating a new table
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $exampleColumns =
            "latin,string,Latin Name\n" .
            "common,string,Common Name\n" .
            "lifespan,int,Lifespan (years)";

        $exampleData =
            "Mercenaria mercenaria,hard clam,40\n" .
            "Magallana gigas,pacific oyster,30\n" .
            "Patella vulgata,common limpet,20";

        return view('table.create',
            compact('exampleColumns', 'exampleData')
        );
    }

    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|string',
            'data' => 'string|nullable',
        ]);

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

        // Parse and validate the columns specification
        /** @var Column[] $columns */
        $columns = [];
        $column_keys = []; // for checking duplicates
        $colTable = array_map('str_getcsv', explode("\n", $input->columns));

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

        foreach ($colTable as $col) {
            $col = array_map('trim', $col);
            if (count($col) < 2 || strlen($col[0])==0) {
                return $this->backWithErrors(['columns' => "All columns must have at least name and type."]);
            }

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

                $columns[] = new Column([
                    'name' => $col[0],
                    'type' => $col[1],
                    'title' => count($col) >= 3 ? $col[2] : $col[0], // title falls back to =name if not specified,
                ]);
            } catch (\Exception $e) {
                return $this->backWithErrors(['columns' => $e->getMessage()]);
            }
        }
        if (count($columns) == 0) return $this->backWithErrors(['columns' => "Define at least one column"]);

        $rowTable = array_map('str_getcsv', explode("\n", $input->data));

        // Preparing data to insert into the Rows table
        $rowsData = null;
        try {
            $rowsData = array_map(function ($row) use ($columns) {
                if (count($row) != count($columns)) {
                    throw new NotApplicableException("All rows must have " . count($columns) . " fields.");
                }
                $parsed = [];
                foreach ($row as $i => $val) {
                    $key = $columns[$i]->name;
                    if (strlen($val) > 255) {
                        // try to stop people inserting unstructured crap / malformed CSV
                        throw new NotApplicableException("Value for column $key too long.");
                    }
                    $parsed[$key] = $columns[$i]->cast($val);
                }
                return [
                    'data' => json_encode($parsed),
                ];
            }, $rowTable);
        } catch (\Exception $e) {
            return $this->backWithErrors(['columns' => $e->getMessage()]);
        }

        $revisionFields = [
            'note' => "Initial revision of table $u->name/$input->name",
            'columns' => json_encode($columns),
            'row_count' => count($rowsData),
        ];

        $tableFields = [
            'owner_id' => $u->id,
            'revision_id' => 0,
            'name' => $input->name,
            'title' => $input->title,
            'description' => $input->description,
            'license' => $input->license,
            'origin' => $input->origin,
        ];

        \DB::transaction(function () use ($revisionFields, $tableFields, $rowsData) {
            $revision = Revision::create($revisionFields);

            $tableFields['revision_id'] = $revision->id; // to meet the not-null constraint
            $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($rowsData);
        });

        return redirect(route('table.view', ['user' => $u, 'table' => $input->name]));
    }
}