<?php

namespace Tests\Feature;

use App\Models\Order;
use App\Models\Setting;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class TicketingSystemTest extends TestCase
{
    use RefreshDatabase;

    public function test_customer_can_create_ticket_from_own_order(): void
    {
        $customer = $this->createUser('customer');
        $order = $this->createOrderForUser($customer);

        $response = $this->actingAs($customer)->post(route('customer.orders.ticket.store', $order), [
            'subject' => 'Issue with received product',
            'category' => 'order_issue',
            'priority' => 'medium',
            'message' => 'The size is not matching the order.',
        ]);

        $response->assertRedirect();
        $this->assertDatabaseHas('tickets', [
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'status' => Ticket::STATUS_OPEN_UNASSIGNED,
        ]);
        $this->assertDatabaseHas('ticket_messages', [
            'sender_id' => $customer->id,
            'sender_type' => TicketMessage::SENDER_CUSTOMER,
        ]);
    }

    public function test_duplicate_active_ticket_is_blocked(): void
    {
        $customer = $this->createUser('customer');
        $order = $this->createOrderForUser($customer);

        Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Existing ticket',
            'category' => Ticket::CATEGORY_ORDER_ISSUE,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_OPEN_UNASSIGNED,
            'active_ticket_key' => $order->id . ':' . $customer->id,
            'last_reply_at' => now(),
            'last_replied_by' => $customer->id,
        ]);

        $response = $this->actingAs($customer)->post(route('customer.orders.ticket.store', $order), [
            'subject' => 'New attempt',
            'category' => 'other',
            'priority' => 'low',
            'message' => 'Second ticket attempt',
        ]);

        $response->assertSessionHas('error');
        $this->assertEquals(1, Ticket::where('order_id', $order->id)->count());
    }

    public function test_customer_cannot_access_another_customers_ticket(): void
    {
        $customerA = $this->createUser('customer');
        $customerB = $this->createUser('customer');
        $order = $this->createOrderForUser($customerA);

        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customerA->id,
            'subject' => 'Private ticket',
            'category' => Ticket::CATEGORY_ORDER_ISSUE,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_OPEN_UNASSIGNED,
            'active_ticket_key' => $order->id . ':' . $customerA->id,
            'last_reply_at' => now(),
            'last_replied_by' => $customerA->id,
        ]);

        $this->actingAs($customerB)
            ->get(route('customer.tickets.show', $ticket))
            ->assertForbidden();
    }

    public function test_staff_pick_reply_and_customer_close_flow(): void
    {
        Setting::set('tickets_notify_web', '1');
        Setting::set('tickets_notify_sms', '0');
        Setting::set('tickets_notify_whatsapp', '0');
        Setting::set('tickets_notify_on_reply', '1');
        Setting::set('tickets_notify_on_status', '1');

        $customer = $this->createUser('customer');
        $staff = $this->createUser('staff');
        $order = $this->createOrderForUser($customer);

        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Need help',
            'category' => Ticket::CATEGORY_OTHER,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_OPEN_UNASSIGNED,
            'active_ticket_key' => $order->id . ':' . $customer->id,
            'last_reply_at' => now(),
            'last_replied_by' => $customer->id,
        ]);

        $this->actingAs($staff)->post(route('admin.tickets.pick', $ticket))
            ->assertSessionHas('success');

        $this->assertEquals($staff->id, $ticket->fresh()->assigned_to);
        $this->assertEquals(Ticket::STATUS_ASSIGNED, $ticket->fresh()->status);

        $this->actingAs($staff)->post(route('admin.tickets.reply', $ticket), [
            'message' => 'We are checking this for you.',
            'is_internal' => '0',
        ])->assertSessionHas('success');

        $ticket->refresh();
        $this->assertEquals(Ticket::STATUS_ANSWERED, $ticket->status);
        $this->assertDatabaseHas('notifications', [
            'notifiable_type' => User::class,
            'notifiable_id' => $customer->id,
        ]);
        $this->assertDatabaseHas('ticket_messages', [
            'ticket_id' => $ticket->id,
            'sender_id' => $staff->id,
            'sender_type' => TicketMessage::SENDER_ADMIN,
            'is_internal' => 0,
        ]);

        $this->actingAs($customer)->post(route('customer.tickets.close', $ticket))
            ->assertSessionHas('success');

        $this->assertEquals(Ticket::STATUS_CLOSED_BY_CUSTOMER, $ticket->fresh()->status);
    }

    public function test_closed_ticket_reply_restriction_for_customer(): void
    {
        $customer = $this->createUser('customer');
        $order = $this->createOrderForUser($customer);
        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Closed thread',
            'category' => Ticket::CATEGORY_OTHER,
            'priority' => Ticket::PRIORITY_LOW,
            'status' => Ticket::STATUS_CLOSED_BY_CUSTOMER,
            'active_ticket_key' => null,
            'closed_at' => now(),
            'last_reply_at' => now(),
            'last_replied_by' => $customer->id,
        ]);

        $this->actingAs($customer)->post(route('customer.tickets.reply', $ticket), [
            'message' => 'Please reopen this.',
        ])->assertSessionHas('error');
    }

    public function test_customer_can_view_and_mark_ticket_notification_as_read(): void
    {
        Setting::set('tickets_notify_web', '1');
        Setting::set('tickets_notify_sms', '0');
        Setting::set('tickets_notify_whatsapp', '0');
        Setting::set('tickets_notify_on_reply', '1');

        $customer = $this->createUser('customer');
        $staff = $this->createUser('staff');
        $order = $this->createOrderForUser($customer);

        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Need update',
            'category' => Ticket::CATEGORY_OTHER,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_ASSIGNED,
            'assigned_to' => $staff->id,
            'assigned_by' => $staff->id,
            'assigned_at' => now(),
            'active_ticket_key' => $order->id . ':' . $customer->id,
            'last_reply_at' => now(),
            'last_replied_by' => $customer->id,
        ]);

        $this->actingAs($staff)->post(route('admin.tickets.reply', $ticket), [
            'message' => 'Sharing an update for your ticket.',
            'is_internal' => '0',
        ])->assertSessionHas('success');

        $notificationId = $customer->fresh()->unreadNotifications()->value('id');
        $this->assertNotNull($notificationId);

        $this->actingAs($customer)
            ->get(route('customer.notifications.index'))
            ->assertOk()
            ->assertSee('Notifications');

        $this->actingAs($customer)
            ->post(route('customer.notifications.read', $notificationId))
            ->assertRedirect();

        $this->assertDatabaseMissing('notifications', [
            'id' => $notificationId,
            'read_at' => null,
        ]);
    }

    public function test_ticket_inactivity_reminder_is_sent_after_configured_days(): void
    {
        Setting::set('tickets_notify_web', '1');
        Setting::set('tickets_auto_close_enabled', '1');
        Setting::set('tickets_customer_reply_timeout_days', '3');
        Setting::set('tickets_auto_close_grace_days', '1');

        $customer = $this->createUser('customer');
        $staff = $this->createUser('staff');
        $order = $this->createOrderForUser($customer);

        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Waiting for customer reply',
            'category' => Ticket::CATEGORY_OTHER,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_ANSWERED,
            'assigned_to' => $staff->id,
            'assigned_by' => $staff->id,
            'assigned_at' => now()->subDays(4),
            'active_ticket_key' => $order->id . ':' . $customer->id,
            'last_reply_at' => now()->subDays(4),
            'last_replied_by' => $staff->id,
            'customer_reminder_sent_at' => null,
        ]);

        $this->artisan('tickets:process-auto-close')->assertExitCode(0);

        $ticket->refresh();
        $this->assertNotNull($ticket->customer_reminder_sent_at);
        $this->assertEquals(Ticket::STATUS_ANSWERED, $ticket->status);
        $this->assertDatabaseHas('notifications', [
            'notifiable_type' => User::class,
            'notifiable_id' => $customer->id,
        ]);
    }

    public function test_ticket_auto_closes_after_reminder_grace_period(): void
    {
        Setting::set('tickets_notify_web', '1');
        Setting::set('tickets_auto_close_enabled', '1');
        Setting::set('tickets_customer_reply_timeout_days', '3');
        Setting::set('tickets_auto_close_grace_days', '1');

        $customer = $this->createUser('customer');
        $staff = $this->createUser('staff');
        $order = $this->createOrderForUser($customer);

        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Auto-close candidate',
            'category' => Ticket::CATEGORY_OTHER,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_ANSWERED,
            'assigned_to' => $staff->id,
            'assigned_by' => $staff->id,
            'assigned_at' => now()->subDays(5),
            'active_ticket_key' => $order->id . ':' . $customer->id,
            'last_reply_at' => now()->subDays(5),
            'last_replied_by' => $staff->id,
            'customer_reminder_sent_at' => now()->subDays(2),
        ]);

        $this->artisan('tickets:process-auto-close')->assertExitCode(0);

        $ticket->refresh();
        $this->assertEquals(Ticket::STATUS_CLOSED_BY_CUSTOMER, $ticket->status);
        $this->assertNull($ticket->active_ticket_key);
        $this->assertNotNull($ticket->auto_closed_at);
        $this->assertEquals('no_customer_reply_timeout', $ticket->auto_close_reason);
    }

    public function test_customer_reply_resets_auto_close_tracking(): void
    {
        $customer = $this->createUser('customer');
        $order = $this->createOrderForUser($customer);

        $ticket = Ticket::create([
            'order_id' => $order->id,
            'customer_id' => $customer->id,
            'subject' => 'Please confirm',
            'category' => Ticket::CATEGORY_OTHER,
            'priority' => Ticket::PRIORITY_MEDIUM,
            'status' => Ticket::STATUS_ANSWERED,
            'active_ticket_key' => $order->id . ':' . $customer->id,
            'last_reply_at' => now()->subDays(4),
            'last_replied_by' => $customer->id,
            'customer_reminder_sent_at' => now()->subDay(),
            'auto_closed_at' => now()->subDay(),
            'auto_close_reason' => 'no_customer_reply_timeout',
        ]);

        $this->actingAs($customer)->post(route('customer.tickets.reply', $ticket), [
            'message' => 'I am replying now.',
        ])->assertSessionHas('success');

        $ticket->refresh();
        $this->assertEquals(Ticket::STATUS_REOPENED, $ticket->status);
        $this->assertNull($ticket->customer_reminder_sent_at);
        $this->assertNull($ticket->auto_closed_at);
        $this->assertNull($ticket->auto_close_reason);
    }

    private function createOrderForUser(User $user): Order
    {
        return Order::create([
            'user_id' => $user->id,
            'source' => 'online',
            'address_snapshot' => [
                'name' => $user->name,
                'mobile' => $user->mobile,
                'line1' => 'Test line',
                'city' => 'Chennai',
                'state' => 'TN',
                'pin' => '600001',
            ],
            'subtotal' => 1000,
            'discount_total' => 0,
            'tax_total' => 0,
            'shipping_total' => 0,
            'total_payable' => 1000,
            'payment_status' => Order::PAYMENT_PENDING,
            'payment_method' => 'online',
            'status' => Order::STATUS_PLACED,
        ]);
    }

    private function createUser(string $type): User
    {
        static $counter = 1;

        $user = User::create([
            'name' => ucfirst($type) . ' User ' . $counter,
            'mobile' => '900000' . str_pad((string) $counter, 4, '0', STR_PAD_LEFT),
            'email' => strtolower($type) . $counter . '@example.com',
            'password' => bcrypt('password'),
            'user_type' => $type,
        ]);

        $counter++;

        return $user;
    }
}
