<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Services\Documents\DocumentNumberService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

final class MasterDataController extends Controller
{
    public function __construct(private readonly DocumentNumberService $numbers) {}

    public function index(Request $request, string $module): View
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'view');
        $model = new $config['model'];
        $query = $model->newQuery();
        $this->scope($request, $query, $config, $model);

        if ($search = trim((string) $request->query('search'))) {
            $query->where(function ($query) use ($config, $search): void {
                foreach ($config['search'] as $index => $column) {
                    $method = $index === 0 ? 'where' : 'orWhere';
                    $query->{$method}($column, 'like', "%{$search}%");
                }
            });
        }

        $records = $query->latest('id')->paginate(20)->withQueryString();
        $displayOptions = $this->displayOptions($request, $config);

        return view('masters.index', compact('module', 'config', 'records', 'displayOptions'));
    }

    public function create(Request $request, string $module): View
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'create');
        $record = new $config['model'];
        $options = $this->fieldOptions($request, $config);

        return view('masters.form', compact('module', 'config', 'record', 'options'));
    }

    public function store(Request $request, string $module): RedirectResponse
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'create');
        $model = new $config['model'];
        $data = $this->validated($request, $config, $model);
        $data = $this->injectContext($request, $config, $model, $data);

        if (isset($config['auto_number']) && empty($data[$config['auto_number']['field']])) {
            $branchId = (int) ($data['branch_id'] ?? $request->user()->branch_id);
            abort_unless($branchId > 0, 422, 'A branch is required to generate the document number.');
            $data[$config['auto_number']['field']] = $this->numbers->next(
                (int) $request->user()->company_id,
                $branchId,
                $config['auto_number']['type'],
                $data['start_date'] ?? now(),
            );
        }

        $model->fill($data)->save();

        return redirect()->route('masters.index', $module)->with('success', $config['singular'].' created successfully.');
    }

    public function show(Request $request, string $module, int $id): View
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'view');
        $record = $this->findScoped($request, $config, $id);
        $displayOptions = $this->displayOptions($request, $config);

        return view('masters.show', compact('module', 'config', 'record', 'displayOptions'));
    }

    public function edit(Request $request, string $module, int $id): View
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'edit_draft');
        $record = $this->findScoped($request, $config, $id);
        $options = $this->fieldOptions($request, $config);

        return view('masters.form', compact('module', 'config', 'record', 'options'));
    }

    public function update(Request $request, string $module, int $id): RedirectResponse
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'edit_draft');
        $record = $this->findScoped($request, $config, $id);
        $record->fill($this->validated($request, $config, $record))->save();

        return redirect()->route('masters.index', $module)->with('success', $config['singular'].' updated successfully.');
    }

    public function destroy(Request $request, string $module, int $id): RedirectResponse
    {
        $config = $this->module($module);
        $this->authorizeAction($request, $config, 'delete_draft');
        $record = $this->findScoped($request, $config, $id);

        try {
            $record->delete();
        } catch (QueryException) {
            return back()->with('error', 'This record is already used and cannot be deleted. Deactivate it instead.');
        }

        return redirect()->route('masters.index', $module)->with('success', $config['singular'].' deleted.');
    }

    private function module(string $module): array
    {
        $config = config("erp.modules.{$module}");
        abort_unless(is_array($config), 404);

        return $config;
    }

    private function authorizeAction(Request $request, array $config, string $action): void
    {
        abort_unless($request->user()->hasPermission($config['permission'].'.'.$action), 403);
    }

    private function validated(Request $request, array $config, Model $model): array
    {
        $rules = [];
        foreach ($config['fields'] as $name => $field) {
            $fieldRules = explode('|', $field['rules'] ?? 'nullable');
            if (($field['unique'] ?? false) === true) {
                $unique = Rule::unique($model->getTable(), $name)->ignore($model->getKey());
                if (Schema::hasColumn($model->getTable(), 'company_id')) {
                    $unique->where(fn ($query) => $query->where('company_id', auth()->user()->company_id));
                }
                $fieldRules[] = $unique;
            }
            $rules[$name] = $fieldRules;
        }

        $data = $request->validate($rules);
        foreach ($config['fields'] as $name => $field) {
            if (($field['type'] ?? 'text') === 'checkbox') {
                $data[$name] = $request->boolean($name);
            }

            if (($field['type'] ?? null) === 'relation' && ! empty($data[$name])) {
                /** @var Model $related */
                $related = new $field['model'];
                $relatedQuery = $related->newQuery()->whereKey($data[$name]);
                if (Schema::hasColumn($related->getTable(), 'company_id')) {
                    $relatedQuery->where('company_id', $request->user()->company_id);
                }
                if (Schema::hasColumn($related->getTable(), 'branch_id')
                    && $request->user()->branch_id
                    && ! $request->user()->hasPermission('branches.view_all')) {
                    $relatedQuery->where(function ($query) use ($request): void {
                        $query->whereNull('branch_id')->orWhere('branch_id', $request->user()->branch_id);
                    });
                }
                abort_unless($relatedQuery->exists(), 422, "Invalid selection for {$field['label']}.");
            }
        }

        return $data;
    }

    private function injectContext(Request $request, array $config, Model $model, array $data): array
    {
        if (($config['company_scoped'] ?? false) && Schema::hasColumn($model->getTable(), 'company_id')) {
            $data['company_id'] = $request->user()->company_id;
        }
        if (Schema::hasColumn($model->getTable(), 'created_by')) {
            $data['created_by'] = $request->user()->id;
        }
        if (($config['branch_scoped'] ?? false) && Schema::hasColumn($model->getTable(), 'branch_id') && empty($data['branch_id'])) {
            $data['branch_id'] = $request->user()->branch_id;
        }

        return $data;
    }

    private function findScoped(Request $request, array $config, int $id): Model
    {
        $model = new $config['model'];
        $query = $model->newQuery();
        $this->scope($request, $query, $config, $model);

        return $query->findOrFail($id);
    }

    private function scope(Request $request, $query, array $config, Model $model): void
    {
        if (($config['company_scoped'] ?? false) && Schema::hasColumn($model->getTable(), 'company_id')) {
            $query->where($model->getTable().'.company_id', $request->user()->company_id);
        }
        if (($config['branch_scoped'] ?? false)
            && $request->user()->branch_id
            && ! $request->user()->hasPermission('branches.view_all')
            && Schema::hasColumn($model->getTable(), 'branch_id')) {
            $query->where($model->getTable().'.branch_id', $request->user()->branch_id);
        }
    }

    private function fieldOptions(Request $request, array $config): array
    {
        $result = [];
        foreach ($config['fields'] as $name => $field) {
            if (($field['type'] ?? null) === 'select') {
                $result[$name] = $field['options'] ?? [];
            }
            if (($field['type'] ?? null) === 'relation') {
                $result[$name] = $this->relationOptions($request, $field);
            }
        }

        return $result;
    }

    private function displayOptions(Request $request, array $config): array
    {
        return $this->fieldOptions($request, $config);
    }

    private function relationOptions(Request $request, array $field): array
    {
        $model = new $field['model'];
        $query = $model->newQuery();
        if (Schema::hasColumn($model->getTable(), 'company_id')) {
            $query->where('company_id', $request->user()->company_id);
        }
        if (Schema::hasColumn($model->getTable(), 'is_active')) {
            $query->where('is_active', true);
        }

        return $query->orderBy($this->orderColumn($field['option_label']))
            ->limit(1000)
            ->get()
            ->mapWithKeys(fn (Model $record) => [$record->getKey() => $this->optionLabel($record, $field['option_label'])])
            ->all();
    }

    private function orderColumn(string $label): string
    {
        return match ($label) {
            'code_name' => 'code',
            'contract_number_title' => 'contract_number',
            default => $label,
        };
    }

    private function optionLabel(Model $record, string $label): string
    {
        return match ($label) {
            'code_name' => trim(($record->code ?? '').' - '.($record->name ?? '')),
            'contract_number_title' => trim(($record->contract_number ?? '').' - '.($record->title ?? '')),
            default => (string) $record->{$label},
        };
    }
}
