<?php

namespace Tests\Feature\Controllers;

use App\Enums\SpecDocumentSectionType;
use App\Models\SpecDocument;
use App\Models\SpecDocumentSection;
use App\Models\SpecDocumentSectionTemplate;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Storage;
use Mtc\MercuryDataModels\Tenant;
use Tests\DatabaseTestCase;
use Tests\UserForTenant;

class SpecDocumentControllerTest extends DatabaseTestCase
{
    use UserForTenant;

    private function document(array $attributes = []): SpecDocument
    {
        return SpecDocument::query()->create(array_merge([
            'tenant_id' => tenant('id'),
            'title' => 'Spec ' . uniqid(),
        ], $attributes));
    }

    private function signedOffDocument(): SpecDocument
    {
        return $this->document([
            'signed_off_at' => now(),
            'signed_off_by' => $this->getUser()->id,
        ]);
    }

    public function testIndexReturnsDocumentsWithProgress(): void
    {
        $document = $this->document();
        $document->sections()->create(['type' => 'notes', 'title' => 'Notes', 'completed_at' => now()]);
        $document->sections()->create(['type' => 'custom', 'title' => 'Extra']);

        $response = $this->actingAs($this->getUser())
            ->getJson(route('spec-documents.index', [], false));

        $response->assertStatus(200);
        $this->assertCount(1, $response->json('data'));
        $this->assertSame(2, $response->json('data.0.sections_count'));
        $this->assertSame(1, $response->json('data.0.completed_sections_count'));
        $this->assertFalse($response->json('data.0.is_signed_off'));
    }

    public function testStoreCreatesDocumentWithPredefinedSections(): void
    {
        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.store', [], false), [
                'tenant_id' => tenant('id'),
                'title' => 'Requirements Overview',
                'version' => '1.0',
            ]);

        $response->assertStatus(201);
        $this->assertSame('Requirements Overview', $response->json('title'));
        $this->assertCount(count(SpecDocumentSectionType::predefined()), $response->json('sections'));
        $this->assertSame('introduction', $response->json('sections.0.type'));

        $document = SpecDocument::query()->where('title', 'Requirements Overview')->first();
        $this->assertNotNull($document);
        $this->assertFalse($document->isSignedOff());
        // Author defaults to the creating user, and is editable thereafter.
        $this->assertSame($this->getUser()->id, $document->author_id);
    }

    public function testStoreSeedsOneIntegrationEntryPerType(): void
    {
        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.store', [], false), [
                'tenant_id' => tenant('id'),
                'title' => 'Requirements Overview',
            ]);

        $response->assertStatus(201);

        $integrations = collect($response->json('sections'))->firstWhere('type', 'integrations');
        $this->assertNotNull($integrations);

        $entries = collect($integrations['data']['integrations'] ?? []);
        $this->assertNotEmpty($entries);
        // One entry per unique type label, with type set and provider blank.
        $this->assertContains('Sales Channels', $entries->pluck('type'));
        $this->assertSame($entries->pluck('type')->unique()->count(), $entries->count());
        $this->assertNull($entries->first()['provider']);
    }

    public function testStoreAutoFillsTemplatableSectionsFromTemplate(): void
    {
        SpecDocumentSectionTemplate::query()->create([
            'type' => 'change-control',
            'data' => ['change_control' => '<p>Standard change control terms.</p>'],
        ]);

        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.store', [], false), [
                'tenant_id' => tenant('id'),
                'title' => 'Requirements Overview',
            ]);

        $response->assertStatus(201);
        $changeControl = collect($response->json('sections'))->firstWhere('type', 'change-control');
        $this->assertNotNull($changeControl);
        $this->assertSame(
            '<p>Standard change control terms.</p>',
            $changeControl['data']['change_control'] ?? null
        );
    }

    public function testSectionTemplateUpdateAndIndex(): void
    {
        $update = $this->actingAs($this->getUser())
            ->putJson(route('spec-document-templates.update', 'assumptions', false), [
                'data' => ['assumptions' => '<p>Launch as one stage.</p>'],
            ]);

        $update->assertStatus(200);
        $assumptions = collect($update->json('templates'))->firstWhere('type', 'assumptions');
        $this->assertSame('<p>Launch as one stage.</p>', $assumptions['data']['assumptions'] ?? null);

        // Only templatable types are listed.
        $types = collect($update->json('templates'))->pluck('type');
        $this->assertContains('introduction', $types);
        $this->assertContains('change-control', $types);
        $this->assertNotContains('feeds', $types);
    }

    public function testSectionTemplateRejectsNonTemplatableType(): void
    {
        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-document-templates.update', 'feeds', false), [
                'data' => ['inbound_feeds' => []],
            ]);

        $response->assertStatus(404);
    }

    public function testSectionTemplateStripsTenantFields(): void
    {
        $this->actingAs($this->getUser())
            ->putJson(route('spec-document-templates.update', 'introduction', false), [
                'data' => [
                    'overview' => '<p>Premium showroom.</p>',
                    'target_launch_date' => '2026-10-01',
                    'pre_launch_monthly_enquiries' => 799,
                ],
            ])->assertStatus(200);

        $template = SpecDocumentSectionTemplate::query()->where('type', 'introduction')->first();
        $this->assertArrayHasKey('overview', $template->data);
        $this->assertArrayNotHasKey('target_launch_date', $template->data);
        $this->assertArrayNotHasKey('pre_launch_monthly_enquiries', $template->data);
    }

    public function testStoreRejectsDuplicateForSite(): void
    {
        $this->document();

        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.store', [], false), [
                'tenant_id' => tenant('id'),
                'title' => 'Second document',
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors(['tenant_id']);
    }

    public function testTargetLaunchDateAndStatsSyncToTenant(): void
    {
        $created = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.store', [], false), [
                'tenant_id' => tenant('id'),
                'title' => 'Requirements Overview',
            ]);
        $created->assertStatus(201);

        $intro = collect($created->json('sections'))->firstWhere('type', 'introduction');
        $this->assertNotNull($intro);

        $update = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.update', [$created->json('id'), $intro['id']], false), [
                'data' => [
                    'target_launch_date' => '2026-10-01',
                    'pre_launch_monthly_enquiries' => 799,
                    'pre_launch_daily_enquiries' => 26,
                    'pre_launch_daily_visitors' => 1400,
                    'current_statistics' => [['label' => 'Page views', 'value' => '4,135,636']],
                ],
            ]);
        $update->assertStatus(200);

        $tenant = Tenant::query()->find(tenant('id'));
        $this->assertSame('2026-10-01', $tenant->proposed_launch_date->format('Y-m-d'));
        $this->assertSame(799, $tenant->pre_launch_monthly_enquiries);
        $this->assertSame(1400, $tenant->pre_launch_daily_visitors);

        // Response reflects tenant values and keeps the custom stats in section data.
        $this->assertSame('2026-10-01', $update->json('data.target_launch_date'));
        $this->assertSame('Page views', $update->json('data.current_statistics.0.label'));

        // Tenant-linked keys are not duplicated into the stored section data.
        $section = SpecDocumentSection::query()->find($intro['id']);
        $this->assertArrayNotHasKey('target_launch_date', $section->data);
        $this->assertArrayHasKey('current_statistics', $section->data);
    }

    public function testOptionsExcludesSitesWithDocument(): void
    {
        $this->document();

        $response = $this->actingAs($this->getUser())
            ->getJson(route('spec-documents.options', [], false));

        $response->assertStatus(200);
        $siteIds = collect($response->json('sites'))->pluck('id');
        $this->assertNotContains(tenant('id'), $siteIds);
    }

    public function testStoreRequiresExistingTenant(): void
    {
        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.store', [], false), [
                'tenant_id' => 'missing-tenant',
                'title' => 'Requirements Overview',
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors(['tenant_id']);
    }

    public function testUpdatePersistsTitleAndVersion(): void
    {
        $document = $this->document(['title' => 'Old']);
        $author = $this->getUser();

        $response = $this->actingAs($author)
            ->putJson(route('spec-documents.update', $document->id, false), [
                'title' => 'New Title',
                'version' => '2.0',
                'author_id' => $author->id,
            ]);

        $response->assertStatus(200);
        $this->assertSame($author->id, $response->json('author_id'));
        $this->assertSame($author->name, $response->json('author'));
        $document->refresh();
        $this->assertSame('New Title', $document->title);
        $this->assertSame('2.0', $document->version);
        $this->assertSame($author->id, $document->author_id);
    }

    public function testUpdateRejectsNonMtcAuthor(): void
    {
        $document = $this->document();
        $nonMtc = \Mtc\MercuryDataModels\User::factory()->create();

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.update', $document->id, false), [
                'author_id' => $nonMtc->id,
            ]);

        $response->assertStatus(422);
    }

    public function testUpdateRejectedAfterSignOff(): void
    {
        $document = $this->signedOffDocument();

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.update', $document->id, false), ['title' => 'New Title']);

        $response->assertStatus(403);
    }

    public function testSignOffRejectedWhenSectionsIncomplete(): void
    {
        $document = $this->document();
        $document->sections()->create(['type' => 'notes', 'title' => 'Notes']);

        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.sign-off', $document->id, false));

        $response->assertStatus(422);
        $this->assertNull($document->refresh()->signed_off_at);
    }

    public function testSignOffLocksDocument(): void
    {
        $document = $this->document();
        $document->sections()->create(['type' => 'notes', 'title' => 'Notes', 'completed_at' => now()]);

        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.sign-off', $document->id, false), [
                'sign_off_notes' => 'Approved by client',
            ]);

        $response->assertStatus(200);
        $this->assertTrue($response->json('is_signed_off'));

        $document->refresh();
        $this->assertNotNull($document->signed_off_at);
        $this->assertSame($this->getUser()->id, $document->signed_off_by);
        $this->assertSame('Approved by client', $document->sign_off_notes);

        $repeat = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.sign-off', $document->id, false));
        $repeat->assertStatus(403);
    }

    public function testDestroyRejectedAfterSignOff(): void
    {
        $document = $this->signedOffDocument();

        $response = $this->actingAs($this->getUser())
            ->deleteJson(route('spec-documents.destroy', $document->id, false));

        $response->assertStatus(403);
        $this->assertNotNull(SpecDocument::query()->find($document->id));
    }

    public function testDestroyRemovesDocument(): void
    {
        $document = $this->document();

        $response = $this->actingAs($this->getUser())
            ->deleteJson(route('spec-documents.destroy', $document->id, false));

        $response->assertStatus(200);
        $this->assertNull(SpecDocument::query()->find($document->id));
    }

    public function testSectionUpdatePersistsDataAndCompletion(): void
    {
        $document = $this->document();
        $section = $document->sections()->create(['type' => 'company-information', 'title' => 'Company Info']);

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.update', [$document->id, $section->id], false), [
                'data' => ['company_name' => 'Example Motors', 'region' => 'England'],
                'completed' => true,
            ]);

        $response->assertStatus(200);
        $section->refresh();
        $this->assertSame('Example Motors', $section->data['company_name']);
        $this->assertNotNull($section->completed_at);
        $this->assertSame($this->getUser()->id, $section->completed_by);

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.update', [$document->id, $section->id], false), [
                'completed' => false,
            ]);

        $response->assertStatus(200);
        $section->refresh();
        $this->assertNull($section->completed_at);
        $this->assertNull($section->completed_by);
    }

    public function testSectionUpdateRejectedAfterSignOff(): void
    {
        $document = $this->signedOffDocument();
        $section = $document->sections()->create(['type' => 'notes', 'title' => 'Notes']);

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.update', [$document->id, $section->id], false), [
                'data' => ['notes' => 'Change'],
            ]);

        $response->assertStatus(403);
    }

    public function testSectionUpdateRejectsSectionFromAnotherDocument(): void
    {
        $document = $this->document();
        $other = $this->document();
        $section = $other->sections()->create(['type' => 'notes', 'title' => 'Notes']);

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.update', [$document->id, $section->id], false), [
                'data' => ['notes' => 'Change'],
            ]);

        $response->assertStatus(404);
    }

    public function testCustomSectionCanBeAddedAndRemoved(): void
    {
        $document = $this->document();

        $response = $this->actingAs($this->getUser())
            ->postJson(route('spec-documents.sections.store', $document->id, false), [
                'type' => 'custom',
                'title' => 'EV Strategy',
                'data' => ['content' => 'EV data on used vehicle pages'],
            ]);

        $response->assertStatus(201);
        $sectionId = $response->json('id');
        $this->assertSame('EV Strategy', $response->json('title'));

        $destroy = $this->actingAs($this->getUser())
            ->deleteJson(route('spec-documents.sections.destroy', [$document->id, $sectionId], false));

        $destroy->assertStatus(204);
        $this->assertCount(0, $document->refresh()->sections);
    }

    public function testReorderCustomSections(): void
    {
        $document = $this->document();
        $first = $document->sections()->create(['type' => 'custom', 'title' => 'A', 'sort_order' => 200]);
        $second = $document->sections()->create(['type' => 'custom', 'title' => 'B', 'sort_order' => 210]);

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.reorder', $document->id, false), [
                'section_ids' => [$second->id, $first->id],
            ]);

        $response->assertStatus(200);
        $order = collect($response->json('sections'))->pluck('id');
        $this->assertLessThan($order->search($first->id), $order->search($second->id));
    }

    public function testReorderRejectedAfterSignOff(): void
    {
        $document = $this->signedOffDocument();
        $section = $document->sections()->create(['type' => 'custom', 'title' => 'A']);

        $response = $this->actingAs($this->getUser())
            ->putJson(route('spec-documents.sections.reorder', $document->id, false), [
                'section_ids' => [$section->id],
            ]);

        $response->assertStatus(403);
    }

    public function testPredefinedSectionCannotBeRemoved(): void
    {
        $document = $this->document();
        $section = $document->sections()->create(['type' => 'design', 'title' => 'Design']);

        $response = $this->actingAs($this->getUser())
            ->deleteJson(route('spec-documents.sections.destroy', [$document->id, $section->id], false));

        $response->assertStatus(422);
    }

    public function testAttachmentUploadAndRemoval(): void
    {
        Storage::fake(Config::get('filesystems.default_media'));
        $document = $this->document();

        $response = $this->actingAs($this->getUser())
            ->post(route('spec-documents.attachments.store', $document->id, false), [
                'file' => UploadedFile::fake()->create('brand-guidelines.pdf', 100, 'application/pdf'),
            ]);

        $response->assertStatus(201);
        $this->assertSame('brand-guidelines.pdf', $response->json('name'));

        $attachment = $document->attachments()->first();
        $this->assertNotNull($attachment);
        Storage::disk(Config::get('filesystems.default_media'))->assertExists($attachment->path);

        $destroy = $this->actingAs($this->getUser())
            ->deleteJson(route('spec-documents.attachments.destroy', [$document->id, $attachment->id], false));

        $destroy->assertStatus(204);
        Storage::disk(Config::get('filesystems.default_media'))->assertMissing($attachment->path);
        $this->assertCount(0, $document->refresh()->attachments);
    }

    public function testAttachmentUploadRejectedAfterSignOff(): void
    {
        Storage::fake(Config::get('filesystems.default_media'));
        $document = $this->signedOffDocument();

        $response = $this->actingAs($this->getUser())
            ->post(route('spec-documents.attachments.store', $document->id, false), [
                'file' => UploadedFile::fake()->create('brand-guidelines.pdf', 100, 'application/pdf'),
            ]);

        $response->assertStatus(403);
    }

    public function testOptionsReturnsBlueprintsAndSources(): void
    {
        // A non-mtc user must not appear in the author selector list.
        $nonMtc = \Mtc\MercuryDataModels\User::factory()->create();

        $response = $this->actingAs($this->getUser())
            ->getJson(route('spec-documents.options', [], false));

        $response->assertStatus(200);

        $types = collect($response->json('section_types'))->pluck('type');
        foreach (SpecDocumentSectionType::values() as $type) {
            $this->assertContains($type, $types);
        }

        $types = $response->json('sources.integration_types');
        $this->assertNotEmpty($types);
        // Reader-friendly labels, not slugs.
        $this->assertContains('Sales Channels', $types);
        $this->assertNotContains('sales-channels', $types);

        // Providers grouped by the same labels so the provider select can scope to type.
        $byType = $response->json('sources.integration_providers_by_type');
        $this->assertArrayHasKey('Sales Channels', $byType);
        $this->assertNotEmpty($byType['Sales Channels']);

        $this->assertNotEmpty($response->json('sources.stock_providers'));

        $siteIds = collect($response->json('sites'))->pluck('id');
        $this->assertContains(tenant('id'), $siteIds);

        // Only mtc users are offered for the author selector.
        $userIds = collect($response->json('users'))->pluck('id');
        $this->assertContains($this->getUser()->id, $userIds);
        $this->assertNotContains($nonMtc->id, $userIds);
    }

    public function testPreviewLinkIsSignedAndRenders(): void
    {
        $author = $this->getUser();
        $document = $this->document(['author_id' => $author->id]);
        $document->sections()->create([
            'type' => 'notes',
            'title' => 'Additional Notes',
            'data' => ['notes' => '<p>Migrate the existing blog content.</p>'],
            'completed_at' => now(),
        ]);
        $document->sections()->create([
            'type' => 'feeds',
            'title' => 'Feeds',
            'data' => [
                'inbound_feeds' => [[
                    'provider' => 'SalesMaster',
                    'requirements' => 'Feed IDs and credentials.',
                ]],
            ],
        ]);
        $document->sections()->create([
            'type' => 'locations',
            'title' => 'Franchises, Locations & Departments',
            'data' => [
                'franchises' => [['name' => 'Volvo']],
                'locations' => [[
                    'name' => 'Volvo Reading',
                    'franchise' => 'Volvo',
                    'departments' => [['name' => 'Sales', 'phone' => '0118 207 0888']],
                ]],
            ],
        ]);

        $link = $this->actingAs($this->getUser())
            ->getJson(route('spec-documents.preview-link', $document->id, false));

        $link->assertStatus(200);
        $url = $link->json('url');
        $this->assertStringContainsString('signature=', $url);

        $path = str_replace(config('app.url'), '', $url);
        $response = $this->actingAs($this->getUser())->get($path);

        $response->assertStatus(200);
        $response->assertSee($document->title, false);
        // Cover page shows the author (replacing the old status line).
        $response->assertSee('Author', false);
        $response->assertSee($author->name, false);
        $response->assertSee('Migrate the existing blog content.', false);
        // Repeater table + nested department recursion render.
        $response->assertSee('Volvo Reading', false);
        $response->assertSee('0118 207 0888', false);
        // Feeds render as a flat table of scalar entries.
        $response->assertSee('SalesMaster', false);
        $response->assertSee('Feed IDs and credentials.', false);
    }

    public function testPreviewRejectsUnsignedRequest(): void
    {
        $document = $this->document();

        $response = $this->actingAs($this->getUser())
            ->get(route('spec-documents.preview', $document->id, false));

        $response->assertStatus(403);
    }

    public function testNonMtcUserCannotAccess(): void
    {
        $document = $this->document();

        $response = $this->actingAs($this->getUser('editor'))
            ->getJson(route('spec-documents.show', $document->id, false));

        $response->assertStatus(403);
    }
}
