<?php

namespace App\Http\Requests;

use App\Services\PageBulkUpdateService;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class PageBulkUpdateRequest extends FormRequest
{
    private ?array $allowedFields = null;

    public function authorize(): bool
    {
        return Auth::check();
    }

    public function rules(): array
    {
        $allowedFields = $this->getAllowedFields();

        return [
            'mode' => ['required', 'string', Rule::in(['same', 'different'])],
            'version_action' => ['nullable', 'string', Rule::in(['publish', 'draft'])],
            'page_ids' => ['required', 'array', 'max:100'],
            'page_ids.*' => ['required', 'integer', 'exists:pages,id'],

            // Same mode validation
            'field' => [
                'required_if:mode,same',
                'nullable',
                'string',
                Rule::in($allowedFields),
            ],
            'value' => ['required_if:mode,same', 'nullable'],

            // Different mode validation
            'updates' => ['required_if:mode,different', 'nullable', 'array'],
            'updates.*' => ['array'],
            'updates.*.*' => ['nullable'],
        ];
    }

    public function withValidator($validator): void
    {
        $validator->after(function ($validator) {
            $service = app(PageBulkUpdateService::class);
            $rules = $service->getValidationRules();
            $labels = $this->fieldLabels($service);

            if ($this->input('mode') === 'same') {
                $this->validateFieldValue(
                    $validator,
                    (string) $this->input('field'),
                    $this->input('value'),
                    'value',
                    $rules,
                    $labels
                );
            }

            if ($this->input('mode') === 'different') {
                $this->validateDifferentModeUpdates($validator);
                $this->validateDifferentModeValues($validator, $rules, $labels);
            }

            $this->validatePublishPermission($validator);
        });
    }

    /**
     * Publishing a page (status => published) requires the publish-content
     * permission, matching the single-edit gate. A "Save as version" (draft)
     * action never publishes — status is coerced to draft server-side — so it
     * is exempt. Without this, edit-content alone (e.g. the Writer role) could
     * bulk-publish pages, bypassing the versioning review flow.
     */
    private function validatePublishPermission($validator): void
    {
        if ($this->input('version_action', 'publish') === 'draft') {
            return;
        }

        if (Auth::user()?->can('publish-content')) {
            return;
        }

        if (!$this->wouldPublishAnyPage()) {
            return;
        }

        $validator->errors()->add('status', 'You do not have permission to publish content.');
    }

    private function wouldPublishAnyPage(): bool
    {
        if ($this->input('mode') === 'same') {
            return $this->input('field') === 'status' && $this->input('value') === 'published';
        }

        foreach ($this->input('updates', []) as $fields) {
            if (is_array($fields) && ($fields['status'] ?? null) === 'published') {
                return true;
            }
        }

        return false;
    }

    /**
     * Validate every submitted per-row value against its field's rules, so
     * "different" mode is held to the same field-level constraints as the
     * per-page edit form (SEO lengths, valid dates, JSON schema, etc.).
     */
    private function validateDifferentModeValues($validator, array $rules, array $labels): void
    {
        foreach ($this->input('updates', []) as $pageId => $fields) {
            if (!is_array($fields)) {
                continue;
            }

            foreach ($fields as $field => $value) {
                $this->validateFieldValue(
                    $validator,
                    (string) $field,
                    $value,
                    "updates.{$pageId}.{$field}",
                    $rules,
                    $labels
                );
            }
        }
    }

    /**
     * Run a single submitted value through its field's rule set via a nested
     * validator, copying any failures onto the parent under $errorKey. Fields
     * with no rule entry are left untouched.
     */
    private function validateFieldValue(
        $validator,
        string $field,
        mixed $value,
        string $errorKey,
        array $rules,
        array $labels
    ): void {
        $config = $rules[$field] ?? null;
        if ($config === null) {
            return;
        }

        $ruleSet = ['value' => $config['rules']];
        if (!empty($config['each'])) {
            $ruleSet['value.*'] = $config['each'];
        }

        $attribute = $labels[$field] ?? $field;
        $nested = Validator::make(
            ['value' => $value],
            $ruleSet,
            [],
            ['value' => $attribute, 'value.*' => $attribute]
        );

        foreach ($nested->errors()->all() as $message) {
            $validator->errors()->add($errorKey, $message);
        }
    }

    private function fieldLabels(PageBulkUpdateService $service): array
    {
        return collect($service->getFields())->pluck('label', 'name')->all();
    }

    private function validateDifferentModeUpdates($validator): void
    {
        $updates = $this->input('updates', []);
        $pageIds = $this->input('page_ids', []);
        $allowedFields = $this->getAllowedFields();

        foreach ($updates as $pageId => $fields) {
            if (!in_array((int) $pageId, array_map('intval', $pageIds), true)) {
                $validator->errors()->add(
                    "updates.{$pageId}",
                    "Page ID {$pageId} is not in the selected pages list."
                );
                continue;
            }

            if (!is_array($fields)) {
                continue;
            }

            foreach (array_keys($fields) as $field) {
                if (!in_array($field, $allowedFields, true)) {
                    $validator->errors()->add(
                        "updates.{$pageId}.{$field}",
                        "Field '{$field}' is not allowed for bulk update."
                    );
                }
            }
        }
    }

    private function getAllowedFields(): array
    {
        if ($this->allowedFields === null) {
            $service = app(PageBulkUpdateService::class);
            $this->allowedFields = $service->getAllowedFields();
        }

        return $this->allowedFields;
    }

    public function messages(): array
    {
        return [
            'mode.required' => 'Please select an update mode.',
            'mode.in' => 'Invalid update mode. Must be "same" or "different".',
            'page_ids.required' => 'Please select at least one page.',
            'page_ids.max' => 'You can only update up to 100 pages at a time.',
            'page_ids.*.exists' => 'One or more selected pages do not exist.',
            'field.required_if' => 'Please select a field to update.',
            'field.in' => 'The selected field is not allowed for bulk update.',
            'value.required_if' => 'Please provide a value for the selected field.',
            'updates.required_if' => 'Please provide updates for the selected pages.',
        ];
    }
}
