<?php

namespace Tests\Tenant;

use App\Jobs\ProcessIncomingEnquiryJob;
use Carbon\Carbon;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Bus;
use Mtc\Crm\Facades\Enquiries;
use Mtc\Crm\Models\Enquiry;
use Mtc\Crm\Models\EnquiryAction;
use Mtc\Crm\Models\FormQuestion;
use Mtc\Crm\Models\FormSection;
use Mtc\MercuryDataModels\Form;
use Tests\TenantTestCase;

class PartialSubmissionsTest extends TenantTestCase
{
    // -------------------------------------------------------------------------
    // IngestEnquiries — hasSufficientDraftData
    // -------------------------------------------------------------------------

    public function test_partial_enquiry_is_dispatched_when_draft_field_is_populated(): void
    {
        Bus::fake();

        $form = Form::factory()->create(['data' => ['partial_submissions' => true]]);
        $section = FormSection::factory()->create(['form_id' => $form->id]);
        $question = FormQuestion::factory()->create([
            'form_id' => $form->id,
            'form_section_id' => $section->id,
            'data' => ['include_in_draft' => true],
        ]);

        $enquiry = Enquiry::factory()->create([
            'form_id' => $form->id,
            'status_id' => 0,
            'ingested_at' => null,
            'updated_at' => Carbon::now()->subMinutes(10),
            'details' => [$question->id => 'John'],
        ]);

        $this->setSetting('crm-ingest-partial-submissions-minutes', 5);

        Artisan::call('enquiries:ingest');

        Bus::assertDispatched(ProcessIncomingEnquiryJob::class);
    }

    public function test_partial_enquiry_is_skipped_when_no_draft_fields_are_populated(): void
    {
        Bus::fake();

        $form = Form::factory()->create(['data' => ['partial_submissions' => true]]);
        $section = FormSection::factory()->create(['form_id' => $form->id]);
        FormQuestion::factory()->create([
            'form_id' => $form->id,
            'form_section_id' => $section->id,
            'data' => ['include_in_draft' => true],
        ]);

        Enquiry::factory()->create([
            'form_id' => $form->id,
            'status_id' => 0,
            'ingested_at' => null,
            'updated_at' => Carbon::now()->subMinutes(10),
            'details' => [], // nothing filled in
        ]);

        $this->setSetting('crm-ingest-partial-submissions-minutes', 5);

        Artisan::call('enquiries:ingest');

        Bus::assertNotDispatched(ProcessIncomingEnquiryJob::class);
    }

    public function test_partial_enquiry_is_skipped_when_form_has_no_draft_questions(): void
    {
        Bus::fake();

        $form = Form::factory()->create(['data' => ['partial_submissions' => true]]);
        $section = FormSection::factory()->create(['form_id' => $form->id]);
        // Question exists but include_in_draft is not set
        FormQuestion::factory()->create([
            'form_id' => $form->id,
            'form_section_id' => $section->id,
            'data' => [],
        ]);

        Enquiry::factory()->create([
            'form_id' => $form->id,
            'status_id' => 0,
            'ingested_at' => null,
            'updated_at' => Carbon::now()->subMinutes(10),
            'details' => ['some' => 'data'],
        ]);

        $this->setSetting('crm-ingest-partial-submissions-minutes', 5);

        Artisan::call('enquiries:ingest');

        Bus::assertNotDispatched(ProcessIncomingEnquiryJob::class);
    }

    public function test_full_submission_always_passes_sufficient_data_check(): void
    {
        Bus::fake();

        // Full enquiry with no form attached — should still be dispatched
        Enquiry::factory()->create([
            'form_id' => null,
            'status_id' => 1,
            'ingested_at' => null,
            'details' => [],
        ]);

        $this->setSetting('crm-ingest-partial-submissions-minutes', 5);

        Artisan::call('enquiries:ingest');

        Bus::assertDispatched(ProcessIncomingEnquiryJob::class);
    }

    public function test_partial_enquiry_is_not_dispatched_before_inactivity_window(): void
    {
        Bus::fake();

        $form = Form::factory()->create(['data' => ['partial_submissions' => true]]);
        $section = FormSection::factory()->create(['form_id' => $form->id]);
        $question = FormQuestion::factory()->create([
            'form_id' => $form->id,
            'form_section_id' => $section->id,
            'data' => ['include_in_draft' => true],
        ]);

        Enquiry::factory()->create([
            'form_id' => $form->id,
            'status_id' => 0,
            'ingested_at' => null,
            'updated_at' => Carbon::now()->subMinutes(2), // only 2 mins ago
            'details' => [$question->id => 'John'],
        ]);

        $this->setSetting('crm-ingest-partial-submissions-minutes', 5); // window is 5 mins

        Artisan::call('enquiries:ingest');

        Bus::assertNotDispatched(ProcessIncomingEnquiryJob::class);
    }

    // -------------------------------------------------------------------------
    // ProcessIncomingEnquiryJob — per-action trigger routing
    // -------------------------------------------------------------------------

    public function test_partial_only_action_fires_for_partial_enquiry(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 0, 'ingested_at' => null]);

        $partialAction = $this->createAction($enquiry->id, 'send-copy-via-mail', 'partial');
        $fullAction = $this->createAction($enquiry->id, 'send-to-salesforce', 'full');
        $allAction = $this->createAction($enquiry->id, 'send-to-enquiry-max', 'all');

        $this->expectActionsFired(['send-copy-via-mail', 'send-to-enquiry-max']);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNotNull($partialAction->fresh()->processed_at);
        $this->assertNull($fullAction->fresh()->processed_at); // skipped
        $this->assertNotNull($allAction->fresh()->processed_at);
    }

    public function test_full_only_action_fires_for_full_submission(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);

        $partialAction = $this->createAction($enquiry->id, 'send-copy-via-mail', 'partial');
        $fullAction = $this->createAction($enquiry->id, 'send-to-salesforce', 'full');
        $allAction = $this->createAction($enquiry->id, 'send-to-enquiry-max', 'all');

        $this->expectActionsFired(['send-to-salesforce', 'send-to-enquiry-max']);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNull($partialAction->fresh()->processed_at); // skipped
        $this->assertNotNull($fullAction->fresh()->processed_at);
        $this->assertNotNull($allAction->fresh()->processed_at);
    }

    public function test_action_without_trigger_defaults_to_all(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 0, 'ingested_at' => null]);

        // No trigger key in fields — should default to 'all'
        $action = EnquiryAction::factory()->create([
            'enquiry_id' => $enquiry->id,
            'action_name' => 'send-copy-via-mail',
            'data' => [],
            'processed_at' => null,
            'failed_at' => null,
            'attempts' => 0,
        ]);

        $this->expectActionsFired(['send-copy-via-mail']);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNotNull($action->fresh()->processed_at);
    }

    // -------------------------------------------------------------------------
    // ProcessIncomingEnquiryJob — failure isolation across the action chain
    // -------------------------------------------------------------------------

    public function test_failing_action_does_not_block_subsequent_actions(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);

        $failing = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');
        $succeedingA = $this->createAction($enquiry->id, 'send-to-enquiry-max', 'all');
        $succeedingB = $this->createAction($enquiry->id, 'send-to-salesforce', 'all');

        $this->mockHandler('send-copy-via-mail', success: false);
        $this->mockHandler('send-to-enquiry-max', success: true);
        $this->mockHandler('send-to-salesforce', success: true);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNotNull($failing->fresh()->failed_at);
        $this->assertSame(1, $failing->fresh()->attempts);
        $this->assertNotNull($succeedingA->fresh()->processed_at);
        $this->assertNotNull($succeedingB->fresh()->processed_at);
        $this->assertNull($enquiry->fresh()->ingested_at);
    }

    public function test_throwing_action_does_not_block_subsequent_actions(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);

        $throwing = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');
        $succeeding = $this->createAction($enquiry->id, 'send-to-enquiry-max', 'all');

        $throwingHandler = \Mockery::mock(\Mtc\Crm\Contracts\EnquiryAction::class);
        $throwingHandler->shouldReceive('handle')->once()->andThrow(new \RuntimeException('boom'));
        Enquiries::shouldReceive('makeAction')->with('send-copy-via-mail')->andReturn($throwingHandler);

        $this->mockHandler('send-to-enquiry-max', success: true);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNotNull($throwing->fresh()->failed_at);
        $this->assertSame(1, $throwing->fresh()->attempts);
        $this->assertNotNull($succeeding->fresh()->processed_at);
        $this->assertNull($enquiry->fresh()->ingested_at);
    }

    public function test_all_actions_attempted_when_multiple_fail(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);

        $failingA = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');
        $failingB = $this->createAction($enquiry->id, 'send-to-enquiry-max', 'all');
        $succeeding = $this->createAction($enquiry->id, 'send-to-salesforce', 'all');

        $this->mockHandler('send-copy-via-mail', success: false);
        $this->mockHandler('send-to-enquiry-max', success: false);
        $this->mockHandler('send-to-salesforce', success: true);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNotNull($failingA->fresh()->failed_at);
        $this->assertSame(1, $failingA->fresh()->attempts);
        $this->assertNotNull($failingB->fresh()->failed_at);
        $this->assertSame(1, $failingB->fresh()->attempts);
        $this->assertNotNull($succeeding->fresh()->processed_at);
        $this->assertNull($enquiry->fresh()->ingested_at);
    }

    public function test_ingested_at_set_only_when_every_action_succeeds(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);

        $actionA = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');
        $actionB = $this->createAction($enquiry->id, 'send-to-enquiry-max', 'all');

        $this->mockHandler('send-copy-via-mail', success: true);
        $this->mockHandler('send-to-enquiry-max', success: true);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $this->assertNotNull($actionA->fresh()->processed_at);
        $this->assertNotNull($actionB->fresh()->processed_at);
        $this->assertNotNull($enquiry->fresh()->ingested_at);
    }

    // -------------------------------------------------------------------------
    // ProcessIncomingEnquiryJob — context column truncation (AUTO-1693)
    // enquiry_actions.context is VARCHAR(500); an overlong value must not make
    // the recording update throw and fail the whole job.
    // -------------------------------------------------------------------------

    public function test_long_exception_message_is_truncated_when_recorded_as_context(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);
        $action = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');

        $handler = \Mockery::mock(\Mtc\Crm\Contracts\EnquiryAction::class);
        $handler->shouldReceive('handle')->once()->andThrow(new \RuntimeException(str_repeat('x', 2000)));
        Enquiries::shouldReceive('makeAction')->with('send-copy-via-mail')->andReturn($handler);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $action->refresh();
        $this->assertNotNull($action->failed_at);
        $this->assertSame(1, $action->attempts);
        $this->assertStringStartsWith('Failed due to a system error: ', $action->context);
        $this->assertLessThanOrEqual(500, mb_strlen($action->context));
    }

    public function test_long_failure_reason_is_truncated_when_recorded_as_context(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);
        $action = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');

        $handler = \Mockery::mock(\Mtc\Crm\Contracts\EnquiryAction::class);
        $handler->shouldReceive('handle')->once()->andReturn(false);
        $handler->shouldReceive('failureReason')->andReturn(str_repeat('y', 2000));
        Enquiries::shouldReceive('makeAction')->with('send-copy-via-mail')->andReturn($handler);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $action->refresh();
        $this->assertNotNull($action->failed_at);
        $this->assertStringStartsWith('Failed with reason: ', $action->context);
        $this->assertLessThanOrEqual(500, mb_strlen($action->context));
    }

    public function test_long_success_details_are_truncated_when_recorded_as_context(): void
    {
        $enquiry = Enquiry::factory()->create(['status_id' => 1, 'ingested_at' => null]);
        $action = $this->createAction($enquiry->id, 'send-copy-via-mail', 'all');

        $handler = \Mockery::mock(\Mtc\Crm\Contracts\EnquiryAction::class);
        $handler->shouldReceive('handle')->once()->andReturn(true);
        $handler->shouldReceive('successDetails')->andReturn(str_repeat('z', 2000));
        Enquiries::shouldReceive('makeAction')->with('send-copy-via-mail')->andReturn($handler);

        (new ProcessIncomingEnquiryJob($enquiry))->handle();

        $action->refresh();
        $this->assertNotNull($action->processed_at);
        $this->assertLessThanOrEqual(500, mb_strlen($action->context));
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private function setSetting(string $key, mixed $value): void
    {
        \App\Facades\Settings::update($key, $value);
    }

    private function createAction(int $enquiryId, string $actionName, string $trigger): EnquiryAction
    {
        return EnquiryAction::factory()->create([
            'enquiry_id' => $enquiryId,
            'action_name' => $actionName,
            'data' => ['trigger' => $trigger],
            'processed_at' => null,
            'failed_at' => null,
            'attempts' => 0,
        ]);
    }

    private function expectActionsFired(array $actionNames): void
    {
        $handler = \Mockery::mock(\Mtc\Crm\Contracts\EnquiryAction::class);
        $handler->shouldReceive('handle')->times(count($actionNames))->andReturn(true);
        $handler->shouldReceive('successDetails')->andReturn('ok');

        foreach ($actionNames as $name) {
            Enquiries::shouldReceive('makeAction')->with($name)->andReturn($handler);
        }
    }

    private function mockHandler(string $actionName, bool $success): void
    {
        $handler = \Mockery::mock(\Mtc\Crm\Contracts\EnquiryAction::class);
        $handler->shouldReceive('handle')->once()->andReturn($success);

        if ($success) {
            $handler->shouldReceive('successDetails')->andReturn('ok');
        } else {
            $handler->shouldReceive('failureReason')->andReturn('mocked failure');
        }

        Enquiries::shouldReceive('makeAction')->with($actionName)->andReturn($handler);
    }
}
