<?php

namespace Tests\Feature;

use App\Http\Middleware\VerifySpaCsrf;
use App\Models\Form;
use App\Models\FormNotification;
use App\Models\FormNotificationTranslation;
use App\Models\FormTranslation;
use App\Models\Franchisee;
use App\Models\TranslateTemplate;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class FormUpdatePreviewEndpointTest extends TestCase
{
    use RefreshDatabase;

    private User $superAdmin;
    private Franchisee $franchise;
    private User $franchiseUser;

    protected function setUp(): void
    {
        parent::setUp();

        config()->set('cache.stores.redis', ['driver' => 'array', 'serialize' => false]);
        $this->withoutMiddleware([VerifySpaCsrf::class]);

        $this->superAdmin = User::factory()->create([
            'role' => 'super_admin',
            'franchisee_id' => null,
        ]);

        $this->franchise = Franchisee::create([
            'name' => 'Franchise A',
            'franchise_domain' => 'franchise-a.example',
            'zoho_franchise_id' => 'ZOHO-A',
            'is_custom_domain' => false,
            'owner_name' => 'Owner A',
            'email' => 'owner-a@example.com',
            'phone' => '1234567890',
            'status' => 'active',
            'is_website_approved' => true,
            'language' => 'hi',
        ]);

        $this->franchiseUser = User::factory()->create([
            'franchisee_id' => $this->franchise->id,
            'role' => 'franchise_admin',
        ]);

        $this->ensurePublishedTranslateTemplate('hi');
    }

    private function ensurePublishedTranslateTemplate(string $languageCode): void
    {
        $code = strtolower($languageCode);
        TranslateTemplate::query()->updateOrCreate(
            ['language_code' => $code],
            [
                'language_name' => strtoupper($code),
                'status' => 'Published',
                'published_at' => now(),
            ]
        );
    }

    public function test_super_admins_cannot_call_update_preview(): void
    {
        $form = $this->createGlobalForm();

        $response = $this->actingAs($this->superAdmin, 'sanctum')
            ->getJson("/admin/forms/{$form->id}/update-preview?language=hi");

        $response->assertStatus(403)
            ->assertJsonPath('status', false)
            ->assertJsonPath('message', 'Only franchise users can perform this action.');
    }

    public function test_returns_safe_payload_when_no_global_translation_exists(): void
    {
        $form = $this->createGlobalForm(false);

        $response = $this->actingAs($this->franchiseUser, 'sanctum')
            ->getJson("/admin/forms/{$form->id}/update-preview?language=hi");

        $response->assertOk()
            ->assertJsonPath('data.update_available', false)
            ->assertJsonPath('data.changes', []);
    }

    public function test_returns_diff_with_form_and_notification_changes(): void
    {
        [$globalForm, $franchiseForm] = $this->createAdoptedFranchiseSetup();

        $globalHindi = FormTranslation::where('form_id', $globalForm->id)->where('language', 'hi')->firstOrFail();
        $globalHindi->forceFill([
            'name' => 'अद्यतन फ़ॉर्म नाम',
            'fields' => $this->hindiFields(extraField: true),
            'version' => $globalHindi->version + 1,
        ])->save();

        $globalNotification = FormNotification::where('form_id', $globalForm->id)->firstOrFail();
        $globalNotificationTranslation = FormNotificationTranslation::where('form_notification_id', $globalNotification->id)
            ->where('language', 'hi')
            ->firstOrFail();
        $globalNotificationTranslation->forceFill([
            'subject' => 'अद्यतन विषय',
            'message' => '<p>अद्यतन संदेश</p>',
            'version' => $globalNotificationTranslation->version + 1,
        ])->save();

        $response = $this->actingAs($this->franchiseUser, 'sanctum')
            ->getJson("/admin/forms/{$franchiseForm->id}/update-preview?language=hi");

        $response->assertOk()
            ->assertJsonPath('data.update_available', true)
            ->assertJsonPath('data.form_update_available', true)
            ->assertJsonPath('data.notification_update_available', true)
            ->assertJsonPath('data.language', 'hi');

        $changes = collect($response->json('data.changes'));
        $this->assertNotEmpty($changes, 'Diff should report at least one change');

        $hasFormNameChange = $changes->contains(
            fn ($c) => str_starts_with($c['label'] ?? '', 'Form Details')
                && str_contains($c['label'] ?? '', 'Form Name')
        );
        $hasNotificationChange = $changes->contains(
            fn ($c) => str_starts_with($c['label'] ?? '', 'Notifications')
        );

        $this->assertTrue($hasFormNameChange, 'Diff must include form name change');
        $this->assertTrue($hasNotificationChange, 'Diff must include notification change');
    }

    public function test_diff_payload_is_clean_grouped_and_human_readable(): void
    {
        [$globalForm, $franchiseForm] = $this->createAdoptedFranchiseSetup();

        $globalHindi = FormTranslation::where('form_id', $globalForm->id)->where('language', 'hi')->firstOrFail();
        $globalHindi->forceFill([
            'description' => 'परीक्षण!!',
            'fields' => $this->hindiFields(extraField: true),
            'version' => $globalHindi->version + 1,
        ])->save();

        $globalNotification = FormNotification::where('form_id', $globalForm->id)->firstOrFail();
        $globalNotificationTranslation = FormNotificationTranslation::where('form_notification_id', $globalNotification->id)
            ->where('language', 'hi')
            ->firstOrFail();
        $globalNotificationTranslation->forceFill([
            'subject' => 'नया फॉर्म सबमिशन!!!',
            'message' => '<p>एक नया फॉर्म सबमिशन प्राप्त हुआ है।!!! ok&nbsp;{all_fields}</p><br/>',
            'version' => $globalNotificationTranslation->version + 1,
        ])->save();

        $changes = collect(
            $this->actingAs($this->franchiseUser, 'sanctum')
                ->getJson("/admin/forms/{$franchiseForm->id}/update-preview?language=hi")
                ->assertOk()
                ->json('data.changes')
        );

        $this->assertNotEmpty($changes);

        // 1. Every change must be grouped under one of the three known labels.
        foreach ($changes as $change) {
            $label = (string) ($change['label'] ?? '');
            $this->assertTrue(
                str_starts_with($label, 'Form Details')
                    || str_starts_with($label, 'Form Fields')
                    || str_starts_with($label, 'Notifications'),
                "Diff entry should be grouped: {$label}"
            );
        }

        $allOldNew = $changes->flatMap(fn ($c) => [(string) ($c['old'] ?? ''), (string) ($c['new'] ?? '')])
            ->implode("\n");

        // 2. No technical/internal keys leak into the diff text.
        foreach (['"is_global"', '"checked"', '"id"', '"order"', '"sort_order"', '"created_at"', '"updated_at"', '"deleted_at"', '"adopted_from_global"', '"source_global_version"', '"source_global_translation_id"'] as $forbidden) {
            $this->assertStringNotContainsString($forbidden, $allOldNew, "Diff must not surface {$forbidden}");
        }

        // 3. No raw HTML for the notification message; placeholders are kept.
        $messageEntry = $changes->first(fn ($c) => str_contains((string) ($c['label'] ?? ''), 'message changed'));
        $this->assertNotNull($messageEntry);
        $this->assertStringNotContainsString('<p>', (string) $messageEntry['new']);
        $this->assertStringNotContainsString('<br', (string) $messageEntry['new']);
        $this->assertStringContainsString('{all_fields}', (string) $messageEntry['new']);
        $this->assertStringContainsString('एक नया फॉर्म सबमिशन', (string) $messageEntry['new']);

        // 4. New field is reported as a single, summarised entry — not one entry per property.
        $addedFieldEntries = $changes->filter(
            fn ($c) => str_contains((string) ($c['label'] ?? ''), 'New field added')
        );
        $this->assertCount(1, $addedFieldEntries, 'Each new field must collapse into one entry');

        $addedField = $addedFieldEntries->first();
        $this->assertNull($addedField['old'], 'Added field has no Old value');
        $this->assertStringContainsString('Type: Text', (string) $addedField['new']);
        $this->assertStringContainsString('Required: No', (string) $addedField['new']);
        $this->assertStringNotContainsString('is_global', (string) $addedField['new']);
        $this->assertStringNotContainsString('checked', (string) $addedField['new']);

        // 5. Description shows readable Old/New.
        $descriptionEntry = $changes->first(
            fn ($c) => (string) ($c['label'] ?? '') === 'Form Details — Description changed'
        );
        $this->assertNotNull($descriptionEntry);
        $this->assertSame('परीक्षण!!', (string) $descriptionEntry['new']);

        // 6. Subject diff uses a clean readable label.
        $subjectEntry = $changes->first(
            fn ($c) => str_contains((string) ($c['label'] ?? ''), 'subject changed')
        );
        $this->assertNotNull($subjectEntry);
        $this->assertSame('नया फॉर्म सबमिशन!!!', (string) $subjectEntry['new']);
    }

    public function test_label_and_placeholder_updates_persist_after_save(): void
    {
        $form = $this->createGlobalForm();

        $hindiPayload = $this->hindiFormPayload($form, label: 'नाम', placeholder: 'अपना नाम लिखें');
        $this->actingAs($this->superAdmin, 'sanctum')
            ->postJson("/admin/forms/{$form->id}", $hindiPayload)
            ->assertOk();

        $hindiPayload2 = $this->hindiFormPayload($form, label: 'पूरा नाम', placeholder: 'पूरा नाम लिखें');
        $this->actingAs($this->superAdmin, 'sanctum')
            ->postJson("/admin/forms/{$form->id}", $hindiPayload2)
            ->assertOk();

        $hindiTranslation = FormTranslation::where('form_id', $form->id)
            ->where('language', 'hi')
            ->firstOrFail();

        $steps = $hindiTranslation->fields['steps'] ?? [];
        $this->assertCount(1, $steps, 'There should be exactly one step after re-save');
        $firstField = $steps[0]['fields'][0] ?? null;
        $this->assertNotNull($firstField);
        $this->assertSame('पूरा नाम', $firstField['field'], 'Field label must persist after re-save');
        $this->assertSame('पूरा नाम लिखें', $firstField['placeholder'], 'Placeholder must persist after re-save');
    }

    public function test_franchise_hindi_override_does_not_overwrite_global_label(): void
    {
        $form = $this->createGlobalForm(true);

        $this->actingAs($this->franchiseUser, 'sanctum')
            ->postJson("/admin/forms/{$form->id}", [
                'language' => 'hi',
                'name' => 'फ्रेंचाइज़ फ़ॉर्म',
                'fields' => $this->hindiFields(label: 'फ्रेंचाइज़ नाम', placeholder: 'फ्रेंचाइज़ प्लेसहोल्डर'),
                'pages' => ['home'],
                'status' => 'Published',
                'notifications' => [
                    [
                        'notification_name' => 'Admin',
                        'event_key' => 'form_submission',
                        'from_email' => 'franchise@example.com',
                        'subject' => 'फ्रेंचाइज़ नया सबमिशन',
                        'message' => '<p>फ्रेंचाइज़ संदेश</p>',
                        'status' => 'Published',
                    ],
                ],
            ])->assertOk();

        $globalHindi = FormTranslation::where('form_id', $form->id)
            ->where('language', 'hi')
            ->firstOrFail();
        $globalSteps = $globalHindi->fields['steps'] ?? [];
        $this->assertSame('नाम', $globalSteps[0]['fields'][0]['field']);
        $this->assertEmpty((string) ($globalSteps[0]['fields'][0]['placeholder'] ?? ''));

        $franchiseForm = Form::where('parent_form_id', $form->id)
            ->where('franchisee_id', $this->franchise->id)
            ->firstOrFail();
        $franchiseHindi = FormTranslation::where('form_id', $franchiseForm->id)
            ->where('language', 'hi')
            ->firstOrFail();
        $franchiseSteps = $franchiseHindi->fields['steps'] ?? [];
        $this->assertSame('फ्रेंचाइज़ नाम', $franchiseSteps[0]['fields'][0]['field']);
        $this->assertSame('फ्रेंचाइज़ प्लेसहोल्डर', $franchiseSteps[0]['fields'][0]['placeholder']);
    }

    private function createGlobalForm(bool $withHindi = true): Form
    {
        $this->actingAs($this->superAdmin, 'sanctum')
            ->postJson('/admin/forms/add', $this->englishFormPayload())
            ->assertOk();

        $form = Form::firstOrFail();

        if ($withHindi) {
            $this->actingAs($this->superAdmin, 'sanctum')
                ->postJson("/admin/forms/{$form->id}", $this->hindiFormPayload($form))
                ->assertOk();
        }

        return $form->fresh();
    }

    /**
     * @return array{0: Form, 1: Form}
     */
    private function createAdoptedFranchiseSetup(): array
    {
        $globalForm = $this->createGlobalForm(true);

        $this->actingAs($this->franchiseUser, 'sanctum')
            ->postJson("/admin/forms/{$globalForm->id}/adopt-global", ['language' => 'hi'])
            ->assertOk();

        $franchiseForm = Form::where('parent_form_id', $globalForm->id)
            ->where('franchisee_id', $this->franchise->id)
            ->firstOrFail();

        return [$globalForm, $franchiseForm];
    }

    private function englishFormPayload(): array
    {
        return [
            'language' => 'en',
            'name' => 'Registration Form',
            'description' => 'Sign up form',
            'status' => 'Published',
            'pages' => ['home'],
            'form_setting' => ['success_message' => 'Thanks!'],
            'fields' => [
                'steps' => [
                    [
                        'id' => 'step-1',
                        'heading' => 'Step 1',
                        'subheading' => '',
                        'order' => 1,
                        'fields' => [
                            ['id' => 'name', 'type' => 'text', 'field' => 'Name', 'placeholder' => '', 'required' => true, 'options' => []],
                            ['id' => 'email', 'type' => 'email', 'field' => 'Email', 'placeholder' => '', 'required' => true, 'options' => []],
                        ],
                    ],
                ],
            ],
            'notifications' => [
                [
                    'notification_name' => 'Admin',
                    'event_key' => 'form_submission',
                    'from_email' => 'no-reply@example.com',
                    'subject' => 'New submission',
                    'message' => '<p>Hello {all_fields}</p>',
                    'status' => 'Published',
                ],
            ],
        ];
    }

    private function hindiFormPayload(Form $form, string $label = 'नाम', string $placeholder = ''): array
    {
        $globalNotification = FormNotification::where('form_id', $form->id)->first();

        return [
            'language' => 'hi',
            'name' => 'पंजीकरण फ़ॉर्म',
            'description' => 'साइन अप फ़ॉर्म',
            'status' => 'Published',
            'pages' => ['home'],
            'form_setting' => ['success_message' => 'धन्यवाद!'],
            'fields' => $this->hindiFields(label: $label, placeholder: $placeholder),
            'notifications' => [
                [
                    'id' => $globalNotification?->id,
                    'notification_name' => $globalNotification?->notification_name ?? 'Admin',
                    'event_key' => 'form_submission',
                    'from_email' => $globalNotification?->from_email ?? 'no-reply@example.com',
                    'subject' => 'नया सबमिशन',
                    'message' => '<p>नमस्ते {all_fields}</p>',
                    'status' => 'Published',
                ],
            ],
        ];
    }

    private function hindiFields(bool $extraField = false, string $label = 'नाम', string $placeholder = ''): array
    {
        $fields = [
            ['id' => 'name', 'type' => 'text', 'field' => $label, 'placeholder' => $placeholder, 'required' => true, 'options' => []],
            ['id' => 'email', 'type' => 'email', 'field' => 'ईमेल', 'placeholder' => '', 'required' => true, 'options' => []],
        ];

        if ($extraField) {
            $fields[] = ['id' => 'phone', 'type' => 'text', 'field' => 'फोन', 'placeholder' => '', 'required' => false, 'options' => []];
        }

        return [
            'steps' => [
                [
                    'id' => 'step-1',
                    'heading' => 'चरण 1',
                    'subheading' => '',
                    'order' => 1,
                    'fields' => $fields,
                ],
            ],
        ];
    }
}
