<?php

namespace Tests\Feature;

use App\Mail\AdminOrderPlacedMail;
use App\Mail\CustomerComplaintResponseMail;
use App\Mail\CustomerOrderPlacedMail;
use App\Mail\CustomerOrderStatusUpdatedMail;
use App\Models\AdminPermission;
use App\Models\AdminUser;
use App\Models\Category;
use App\Models\Coupon;
use App\Models\Customer;
use App\Models\InventoryMovement;
use App\Models\Order;
use App\Models\Product;
use App\Models\SiteSetting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class OrderProcessTest extends TestCase
{
    use RefreshDatabase;

    public function test_customer_can_checkout_with_ibft_screenshot_and_cart_is_cleared(): void
    {
        Mail::fake();
        Storage::fake('public');
        SiteSetting::put(SiteSetting::IBFT_IBAN, 'PK00BANK0000000000000000');
        SiteSetting::put(SiteSetting::IBFT_ACCOUNT_TITLE, 'StayCart Pvt Ltd');
        SiteSetting::put(SiteSetting::IBFT_BANK_NAME, 'Demo Bank');
        SiteSetting::put(SiteSetting::SHIPPING_CHARGE, '200');
        SiteSetting::put(SiteSetting::FREE_SHIPPING_MINIMUM, '1000');
        SiteSetting::put(SiteSetting::ADMIN_ORDER_EMAIL_1, 'orders@example.com');
        SiteSetting::put(SiteSetting::ADMIN_ORDER_EMAIL_2, 'owner@example.com');
        Coupon::query()->create([
            'code' => 'SAVE50',
            'type' => Coupon::TYPE_FIXED,
            'value' => 50,
            'minimum_order_amount' => 200,
            'is_active' => true,
        ]);

        $customer = $this->customer();
        $product = $this->product();
        $address = $customer->addresses()->create([
            'label' => 'Home',
            'recipient_name' => 'Order Customer',
            'phone' => '03001234567',
            'city' => 'Lahore',
            'area' => 'Model Town',
            'address_line' => 'House 10',
            'is_default' => true,
        ]);
        $variant = $product->variants()->create([
            'name' => 'Size',
            'value' => 'Large',
            'price' => 125,
            'stock' => 5,
            'is_active' => true,
        ]);
        $customer->cartItems()->create([
            'product_id' => $product->id,
            'product_variant_id' => $variant->id,
            'quantity' => 2,
        ]);

        $this->actingAs($customer, 'customer')
            ->get('/checkout')
            ->assertOk()
            ->assertSee('PK00BANK0000000000000000')
            ->assertSee('StayCart Pvt Ltd')
            ->assertSee('Demo Bank')
            ->assertSee('Delivery address')
            ->assertSee('House 10')
            ->assertSee('Shipping')
            ->assertSee('Rs 200')
            ->assertSee('Rs 450');

        $this->actingAs($customer, 'customer')
            ->post('/checkout', [
                'customer_address_id' => $address->id,
                'coupon_code' => 'save50',
                'payment_method' => 'ibft',
                'payment_screenshot' => UploadedFile::fake()->image('ibft.jpg', 900, 900),
                'customer_note' => 'Paid via IBFT.',
            ])
            ->assertRedirect();

        $order = Order::query()->firstOrFail();

        $this->assertSame(Order::STATUS_PENDING_APPROVAL, $order->status);
        $this->assertSame('ibft', $order->payment_method);
        $this->assertSame('Paid via IBFT.', $order->customer_note);
        $this->assertCount(1, $order->items);
        $this->assertSame($variant->id, $order->items()->first()->product_variant_id);
        $this->assertSame('Size: Large', $order->items()->first()->variant_label);
        $this->assertSame('250.00', $order->subtotal);
        $this->assertSame('200.00', $order->shipping_amount);
        $this->assertSame('SAVE50', $order->coupon_code);
        $this->assertSame('50.00', $order->discount_amount);
        $this->assertSame('400.00', $order->total);
        $this->assertSame($address->id, $order->customer_address_id);
        $this->assertSame('House 10', $order->delivery_address_line);
        $this->assertSame('Lahore', $order->delivery_city);
        $this->assertSame(0, $customer->cartItems()->count());
        Storage::disk('public')->assertExists($order->payment_screenshot_path);
        Mail::assertSent(AdminOrderPlacedMail::class, fn (AdminOrderPlacedMail $mail) => $mail->hasTo('orders@example.com'));
        Mail::assertSent(AdminOrderPlacedMail::class, fn (AdminOrderPlacedMail $mail) => $mail->hasTo('owner@example.com'));
        Mail::assertSent(CustomerOrderPlacedMail::class, fn (CustomerOrderPlacedMail $mail) => $mail->hasTo('order.customer@example.com'));
    }

    public function test_customer_can_checkout_with_cash_on_delivery_when_enabled(): void
    {
        Mail::fake();
        SiteSetting::put(SiteSetting::IBFT_ENABLED, '0');
        SiteSetting::put(SiteSetting::COD_ENABLED, '1');

        $customer = $this->customer('cod.customer@example.com');
        $product = $this->product();
        $address = $customer->addresses()->create([
            'label' => 'Home',
            'recipient_name' => 'COD Customer',
            'phone' => '03009998877',
            'city' => 'Karachi',
            'area' => 'Gulshan',
            'address_line' => 'House 20',
            'is_default' => true,
        ]);
        $customer->cartItems()->create([
            'product_id' => $product->id,
            'quantity' => 1,
        ]);

        $this->actingAs($customer, 'customer')
            ->get('/checkout')
            ->assertOk()
            ->assertSee('Cash on Delivery')
            ->assertDontSee('IBFT Details');

        $this->actingAs($customer, 'customer')
            ->post('/checkout', [
                'customer_address_id' => $address->id,
                'payment_method' => 'cod',
            ])
            ->assertRedirect();

        $order = Order::query()->where('customer_id', $customer->id)->firstOrFail();

        $this->assertSame('cod', $order->payment_method);
        $this->assertNull($order->payment_screenshot_path);
        $this->assertSame(Order::STATUS_PENDING_APPROVAL, $order->status);
        Mail::assertSent(CustomerOrderPlacedMail::class, fn (CustomerOrderPlacedMail $mail) => $mail->hasTo('cod.customer@example.com'));
    }

    public function test_checkout_blocks_disabled_payment_methods(): void
    {
        SiteSetting::put(SiteSetting::IBFT_ENABLED, '0');
        SiteSetting::put(SiteSetting::COD_ENABLED, '1');

        $customer = $this->customer('disabled.payment.customer@example.com');
        $product = $this->product();
        $address = $customer->addresses()->create([
            'label' => 'Home',
            'recipient_name' => 'Payment Customer',
            'phone' => '03001112222',
            'city' => 'Lahore',
            'area' => 'Johar Town',
            'address_line' => 'House 30',
            'is_default' => true,
        ]);
        $customer->cartItems()->create([
            'product_id' => $product->id,
            'quantity' => 1,
        ]);

        $this->actingAs($customer, 'customer')
            ->post('/checkout', [
                'customer_address_id' => $address->id,
                'payment_method' => 'ibft',
                'payment_screenshot' => UploadedFile::fake()->image('ibft.jpg', 900, 900),
            ])
            ->assertSessionHasErrors('payment_method');
    }

    public function test_admin_can_approve_process_and_complete_order(): void
    {
        Mail::fake();

        $admin = $this->orderAdmin();
        $order = $this->order();

        $this->actingAs($admin, 'admin')
            ->get("/admin/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Change order status')
            ->assertSee('Payment Pending')
            ->assertSee('Payment Approval')
            ->assertSee('Process')
            ->assertSee('In Hand Delivery')
            ->assertSee('Review and Rating')
            ->assertSee('Completed')
            ->assertSee('Approval note')
            ->assertSee('Rejection reason');

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/approve", [
                'payment_approval_note' => 'Payment matched IBFT screenshot.',
            ])
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_APPROVED, $order->fresh()->status);
        $this->assertSame('Payment matched IBFT screenshot.', $order->fresh()->payment_approval_note);
        $this->assertSame(9, $order->items()->first()->product->fresh()->stock);
        $this->assertNotNull($order->fresh()->stock_deducted_at);
        Mail::assertSent(CustomerOrderStatusUpdatedMail::class, fn (CustomerOrderStatusUpdatedMail $mail) => $mail->hasTo($order->customer->email) && $mail->order->status === Order::STATUS_APPROVED);
        $this->assertDatabaseHas('inventory_movements', [
            'order_id' => $order->id,
            'product_id' => $order->items()->first()->product_id,
            'type' => InventoryMovement::TYPE_ORDER_DEDUCTION,
            'quantity_change' => -1,
            'stock_before' => 10,
            'stock_after' => 9,
        ]);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/process")
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_PROCESSING, $order->fresh()->status);
        $this->assertSame(9, $order->items()->first()->product->fresh()->stock);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/complete")
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_COMPLETED, $order->fresh()->status);
        Mail::assertSent(CustomerOrderStatusUpdatedMail::class, fn (CustomerOrderStatusUpdatedMail $mail) => $mail->hasTo($order->customer->email) && $mail->order->status === Order::STATUS_COMPLETED);

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Payment Approval Note')
            ->assertSee('Payment matched IBFT screenshot.');
    }

    public function test_admin_can_change_order_status_from_dropdown(): void
    {
        Mail::fake();

        $admin = $this->orderAdmin();
        $order = $this->order();

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/status", [
                'status' => Order::STATUS_APPROVED,
            ])
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_APPROVED, $order->fresh()->status);
        $this->assertNotNull($order->fresh()->approved_at);
        Mail::assertSent(CustomerOrderStatusUpdatedMail::class, fn (CustomerOrderStatusUpdatedMail $mail) => $mail->hasTo($order->customer->email));

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/status", [
                'status' => Order::STATUS_PROCESSING,
            ])
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_PROCESSING, $order->fresh()->status);
        $this->assertNotNull($order->fresh()->processing_at);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/status", [
                'status' => Order::STATUS_IN_HAND_DELIVERY,
            ])
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_IN_HAND_DELIVERY, $order->fresh()->status);
        $this->assertNotNull($order->fresh()->in_hand_delivery_at);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/status", [
                'status' => Order::STATUS_REVIEW_AND_RATING,
            ])
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_REVIEW_AND_RATING, $order->fresh()->status);
        $this->assertNotNull($order->fresh()->review_at);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/status", [
                'status' => Order::STATUS_COMPLETED,
            ])
            ->assertSessionHas('status');
        $this->assertSame(Order::STATUS_COMPLETED, $order->fresh()->status);
        $this->assertNotNull($order->fresh()->completed_at);
        Mail::assertSent(CustomerOrderStatusUpdatedMail::class, 5);
    }

    public function test_customer_and_guest_can_track_order_by_order_number_and_delivery_phone(): void
    {
        $order = $this->order();
        $order->update([
            'status' => Order::STATUS_PROCESSING,
            'delivery_recipient_name' => 'Order Customer',
            'delivery_phone' => '03001234567',
            'delivery_city' => 'Lahore',
            'delivery_area' => 'Model Town',
            'delivery_address_line' => 'House 10',
            'approved_at' => now()->subHours(2),
            'processing_at' => now()->subHour(),
        ]);

        $this->get('/track-order')
            ->assertOk()
            ->assertSee('Track Order')
            ->assertSee('Order Lookup');

        $this->get('/track-order?order_number=ORD-TEST-1&delivery_phone=0000')
            ->assertOk()
            ->assertSee('No order found')
            ->assertDontSee('Orders Product');

        $this->get('/track-order?order_number=ORD-TEST-1&delivery_phone=03001234567')
            ->assertOk()
            ->assertSee('Processing')
            ->assertSee('Payment Approved')
            ->assertSee('In Hand Delivery')
            ->assertSee('Orders Product')
            ->assertSee('House 10');

        $this->actingAs($order->customer, 'customer')
            ->get('/orders')
            ->assertOk()
            ->assertSee('Track')
            ->assertSee(e(route('orders.track', ['order_number' => $order->order_number, 'delivery_phone' => $order->delivery_phone])), false);

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Track')
            ->assertSee(e(route('orders.track', ['order_number' => $order->order_number, 'delivery_phone' => $order->delivery_phone])), false);
    }

    public function test_admin_can_reject_pending_order(): void
    {
        Mail::fake();

        $admin = $this->orderAdmin();
        $order = $this->order();

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/reject")
            ->assertSessionHasErrors('payment_rejection_reason');

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/reject", [
                'payment_rejection_reason' => 'Payment screenshot is not readable.',
            ])
            ->assertSessionHas('status');

        $this->assertSame(Order::STATUS_REJECTED, $order->fresh()->status);
        $this->assertSame('Payment screenshot is not readable.', $order->fresh()->payment_rejection_reason);
        Mail::assertSent(CustomerOrderStatusUpdatedMail::class, fn (CustomerOrderStatusUpdatedMail $mail) => $mail->hasTo($order->customer->email)
            && $mail->order->payment_rejection_reason === 'Payment screenshot is not readable.');

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Payment Rejection Reason')
            ->assertSee('Payment screenshot is not readable.');
    }

    public function test_admin_approval_deducts_variant_stock_once(): void
    {
        $admin = $this->orderAdmin();
        $customer = $this->customer('variant.stock.customer@example.com');
        $product = $this->product();
        $variant = $product->variants()->create([
            'name' => 'Color',
            'value' => 'Black',
            'price' => 120,
            'stock' => 4,
            'is_active' => true,
        ]);

        $order = $customer->orders()->create([
            'order_number' => 'ORD-STOCK-1',
            'status' => Order::STATUS_PENDING_APPROVAL,
            'payment_method' => 'ibft',
            'payment_screenshot_path' => 'payment-screenshots/test.jpg',
            'subtotal' => 240,
            'total' => 240,
        ]);

        $order->items()->create([
            'product_id' => $product->id,
            'product_variant_id' => $variant->id,
            'product_name' => $product->name,
            'variant_label' => $variant->label(),
            'unit_price' => 120,
            'quantity' => 2,
            'line_total' => 240,
        ]);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/approve", [
                'payment_approval_note' => 'Variant stock payment approved.',
            ])
            ->assertSessionHas('status');

        $this->assertSame(2, $variant->fresh()->stock);

        $this->actingAs($admin, 'admin')
            ->patch("/admin/orders/{$order->id}/process")
            ->assertSessionHas('status');

        $this->assertSame(2, $variant->fresh()->stock);
    }

    public function test_customer_can_upload_review_image_and_video_for_review_order(): void
    {
        Storage::fake('public');

        $admin = $this->orderAdmin();
        $order = $this->order();
        $order->update(['status' => Order::STATUS_REVIEW_AND_RATING]);

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Product Feedback')
            ->assertSee('Orders Product')
            ->assertSee('Review image')
            ->assertSee('Review video');

        $productId = $order->items()->firstOrFail()->product_id;

        $this->actingAs($order->customer, 'customer')
            ->post("/orders/{$order->id}/review", [
                'product_id' => $productId,
                'rating' => 5,
                'comment' => 'Excellent delivery experience.',
                'review_image' => UploadedFile::fake()->image('review.jpg', 900, 900),
                'review_video' => UploadedFile::fake()->create('review.mp4', 1024, 'video/mp4'),
            ])
            ->assertSessionHas('status');

        $review = $order->fresh()->reviews()->first();

        $this->assertNotNull($review);
        $this->assertSame($productId, $review->product_id);
        $this->assertSame(5, $review->rating);
        $this->assertSame('Excellent delivery experience.', $review->comment);
        Storage::disk('public')->assertExists($review->image_path);
        Storage::disk('public')->assertExists($review->video_path);

        $this->actingAs($admin, 'admin')
            ->get("/admin/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Product Reviews')
            ->assertSee('Orders Product')
            ->assertSee('Excellent delivery experience.')
            ->assertSee($review->imageUrl(), false)
            ->assertSee($review->videoUrl(), false);
    }

    public function test_customer_can_submit_review_and_complaint_after_completed_order(): void
    {
        Mail::fake();

        $admin = $this->orderAdmin();
        $order = $this->order();
        $order->update(['status' => Order::STATUS_COMPLETED]);

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Product Feedback')
            ->assertSee('Submit Product Review')
            ->assertSee('Submit Product Complaint');

        $productId = $order->items()->firstOrFail()->product_id;

        $this->actingAs($order->customer, 'customer')
            ->post("/orders/{$order->id}/review", [
                'product_id' => $productId,
                'rating' => 4,
                'comment' => 'Good product.',
            ])
            ->assertSessionHas('status');

        $this->actingAs($order->customer, 'customer')
            ->post("/orders/{$order->id}/complaint", [
                'product_id' => $productId,
                'subject' => 'Late delivery',
                'message' => 'The package arrived later than expected.',
            ])
            ->assertSessionHas('status');

        $this->assertSame(4, $order->fresh()->reviews()->first()->rating);
        $this->assertSame('Good product.', $order->fresh()->reviews()->first()->comment);
        $this->assertSame('Late delivery', $order->fresh()->complaints()->first()->subject);
        $this->assertSame('The package arrived later than expected.', $order->fresh()->complaints()->first()->message);

        $this->actingAs($admin, 'admin')
            ->get("/admin/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Product Reviews')
            ->assertSee('Good product.')
            ->assertSee('Product Complaints')
            ->assertSee('Orders Product')
            ->assertSee('Late delivery')
            ->assertSee('The package arrived later than expected.')
            ->assertSee('Save Response');

        $complaint = $order->fresh()->complaints()->firstOrFail();

        $this->actingAs($admin, 'admin')
            ->patch("/admin/order-complaints/{$complaint->id}/response", [
                'admin_response' => 'We are checking this with our delivery team.',
            ])
            ->assertSessionHas('status');

        $this->assertSame('We are checking this with our delivery team.', $complaint->fresh()->admin_response);
        $this->assertNotNull($complaint->fresh()->responded_at);
        Mail::assertSent(CustomerComplaintResponseMail::class, fn (CustomerComplaintResponseMail $mail) => $mail->hasTo($order->customer->email)
            && $mail->complaint->admin_response === 'We are checking this with our delivery team.');

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}")
            ->assertOk()
            ->assertSee('Admin Response')
            ->assertSee('We are checking this with our delivery team.');
    }

    public function test_customer_and_admin_can_view_printable_invoice(): void
    {
        $admin = $this->orderAdmin();
        $order = $this->order();

        $this->actingAs($order->customer, 'customer')
            ->get("/orders/{$order->id}/invoice")
            ->assertOk()
            ->assertSee('Invoice')
            ->assertSee($order->order_number)
            ->assertSee('Orders Product')
            ->assertSee('Subtotal')
            ->assertSee('Shipping')
            ->assertSee('Print Invoice');

        $otherCustomer = $this->customer('other.invoice.customer@example.com');

        $this->actingAs($otherCustomer, 'customer')
            ->get("/orders/{$order->id}/invoice")
            ->assertForbidden();

        $this->actingAs($admin, 'admin')
            ->get("/admin/orders/{$order->id}/invoice")
            ->assertOk()
            ->assertSee('Invoice')
            ->assertSee($order->order_number)
            ->assertSee('Orders Product');
    }

    public function test_admin_without_order_permission_cannot_manage_orders(): void
    {
        $admin = AdminUser::query()->create([
            'name' => 'No Orders Admin',
            'email' => 'no.orders.admin@example.com',
            'password' => Hash::make('secret-password'),
            'is_active' => true,
        ]);

        $permission = AdminPermission::query()->create(['name' => 'Manage Products', 'slug' => 'products.manage']);
        $admin->permissions()->attach($permission);

        $this->actingAs($admin, 'admin')
            ->get('/admin/orders')
            ->assertForbidden();
    }

    private function orderAdmin(): AdminUser
    {
        $admin = AdminUser::query()->create([
            'name' => 'Order Admin',
            'email' => 'order.admin@example.com',
            'password' => Hash::make('secret-password'),
            'is_active' => true,
        ]);

        $admin->permissions()->attach(
            AdminPermission::query()->create(['name' => 'Manage Orders', 'slug' => 'orders.manage'])
        );

        return $admin;
    }

    private function order(): Order
    {
        $customer = $this->customer();
        $product = $this->product();

        $order = $customer->orders()->create([
            'order_number' => 'ORD-TEST-1',
            'status' => Order::STATUS_PENDING_APPROVAL,
            'payment_method' => 'ibft',
            'payment_screenshot_path' => 'payment-screenshots/test.jpg',
            'subtotal' => 100,
            'total' => 100,
        ]);

        $order->items()->create([
            'product_id' => $product->id,
            'product_name' => $product->name,
            'unit_price' => 100,
            'quantity' => 1,
            'line_total' => 100,
        ]);

        return $order;
    }

    private function customer(string $email = 'order.customer@example.com'): Customer
    {
        return Customer::query()->create([
            'name' => 'Order Customer',
            'email' => $email,
            'password' => Hash::make('secret-password'),
            'is_active' => true,
        ]);
    }

    private function product(): Product
    {
        $category = Category::query()->firstOrCreate(
            ['slug' => 'orders-category'],
            ['name' => 'Orders Category', 'is_active' => true]
        );

        return Product::query()->firstOrCreate(
            ['slug' => 'orders-product'],
            [
                'category_id' => $category->id,
                'name' => 'Orders Product',
                'price' => 100,
                'stock' => 10,
                'rating' => 4.5,
                'is_active' => true,
            ]
        );
    }
}
