<?php

declare(strict_types=1);

use App\Models\AuditLog;
use App\Models\Plan;
use App\Models\Service;
use App\Models\User;
use App\Services\Provisioning\ProvisioningFactory;
use App\Services\Provisioning\ProvisioningServiceInterface;
use Database\Seeders\RoleAndPermissionSeeder;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Subscription;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);

    $this->admin = User::factory()->admin()->create();
    $this->customer = User::factory()->customer()->create();

    $this->plan = Plan::factory()->create([
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $this->subscription = Subscription::factory()->create([
        'user_id' => $this->customer->id,
        'type' => 'default',
        'stripe_status' => 'active',
    ]);
});

test('admin can manually provision an unprovision service', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'subscription_id' => $this->subscription->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'pending',
        'provisioned_at' => null,
    ]);

    // Mock the provisioning service
    $mockProvisioningService = Mockery::mock(ProvisioningServiceInterface::class);
    $mockProvisioningService->shouldReceive('provision')
        ->once()
        ->with(Mockery::on(function ($sub) {
            return $sub->id === $this->subscription->id;
        }))
        ->andReturn(Service::factory()->create([
            'user_id' => $this->customer->id,
            'subscription_id' => $this->subscription->id,
            'plan_id' => $this->plan->id,
            'service_type' => 'vps',
            'status' => 'active',
            'provisioned_at' => now(),
            'platform' => 'VirtFusion',
            'platform_service_id' => '12345',
        ]));

    $mockFactory = Mockery::mock(ProvisioningFactory::class);
    $mockFactory->shouldReceive('make')
        ->with('vps')
        ->andReturn($mockProvisioningService);

    $this->app->instance(ProvisioningFactory::class, $mockFactory);

    Log::shouldReceive('info')->once();

    $response = $this->actingAs($this->admin)
        ->post("http://admin.ezscale.dev/services/{$service->id}/provision");

    $response->assertSessionHas('success', 'Service has been provisioned successfully.');

    // Verify audit log was created
    $this->assertDatabaseHas('audit_logs', [
        'admin_id' => $this->admin->id,
        'action' => 'manual_provision_service',
        'resource_type' => 'service',
    ]);
});

test('admin cannot provision an already provisioned service', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'subscription_id' => $this->subscription->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
        'provisioned_at' => now()->subDay(),
    ]);

    $response = $this->actingAs($this->admin)
        ->post("http://admin.ezscale.dev/services/{$service->id}/provision");

    $response->assertSessionHas('error', 'Service has already been provisioned.');

    // Verify no audit log was created
    $this->assertDatabaseMissing('audit_logs', [
        'action' => 'manual_provision_service',
    ]);
});

test('admin cannot provision service without subscription', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'subscription_id' => null,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'pending',
        'provisioned_at' => null,
    ]);

    $response = $this->actingAs($this->admin)
        ->post("http://admin.ezscale.dev/services/{$service->id}/provision");

    $response->assertSessionHas('error', 'Service must have an associated subscription to be provisioned.');
});

test('provision handles provisioning failures gracefully', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'subscription_id' => $this->subscription->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'pending',
        'provisioned_at' => null,
    ]);

    // Mock the provisioning service to throw an exception
    $mockProvisioningService = Mockery::mock(ProvisioningServiceInterface::class);
    $mockProvisioningService->shouldReceive('provision')
        ->once()
        ->andThrow(new \Exception('API connection failed'));

    $mockFactory = Mockery::mock(ProvisioningFactory::class);
    $mockFactory->shouldReceive('make')
        ->with('vps')
        ->andReturn($mockProvisioningService);

    $this->app->instance(ProvisioningFactory::class, $mockFactory);

    Log::shouldReceive('error')->once();

    $response = $this->actingAs($this->admin)
        ->post("http://admin.ezscale.dev/services/{$service->id}/provision");

    $response->assertSessionHas('error');
    expect($response->getSession()->get('error'))->toContain('API connection failed');
});

test('admin can change service plan', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $newPlan = Plan::factory()->create([
        'service_type' => 'vps',
        'status' => 'active',
        'name' => 'VPS Pro',
    ]);

    Log::shouldReceive('info')->once();

    $response = $this->actingAs($this->admin)
        ->put("http://admin.ezscale.dev/services/{$service->id}", [
            'plan_id' => $newPlan->id,
        ]);

    $response->assertSessionHas('success', 'Service has been updated successfully.');

    // Verify plan was changed
    $this->assertDatabaseHas('services', [
        'id' => $service->id,
        'plan_id' => $newPlan->id,
    ]);

    // Verify audit log
    $auditLog = AuditLog::where('action', 'update_service')
        ->where('resource_id', $service->id)
        ->first();

    expect($auditLog)->not->toBeNull();
    expect($auditLog->admin_id)->toBe($this->admin->id);
    expect($auditLog->changes)->toHaveKey('plan');
});

test('admin cannot change service to plan of different service type', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $dedicatedPlan = Plan::factory()->create([
        'service_type' => 'dedicated',
        'status' => 'active',
        'name' => 'Dedicated Server',
    ]);

    $response = $this->actingAs($this->admin)
        ->put("http://admin.ezscale.dev/services/{$service->id}", [
            'plan_id' => $dedicatedPlan->id,
        ]);

    $response->assertSessionHasErrors(['plan_id']);

    // Verify plan was not changed
    $this->assertDatabaseHas('services', [
        'id' => $service->id,
        'plan_id' => $this->plan->id,
    ]);
});

test('admin cannot change service to inactive plan', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $inactivePlan = Plan::factory()->create([
        'service_type' => 'vps',
        'status' => 'inactive',
    ]);

    $response = $this->actingAs($this->admin)
        ->put("http://admin.ezscale.dev/services/{$service->id}", [
            'plan_id' => $inactivePlan->id,
        ]);

    $response->assertSessionHasErrors(['plan_id']);

    // Verify plan was not changed
    $this->assertDatabaseHas('services', [
        'id' => $service->id,
        'plan_id' => $this->plan->id,
    ]);
});

test('admin can update service notes', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $response = $this->actingAs($this->admin)
        ->put("http://admin.ezscale.dev/services/{$service->id}", [
            'notes' => 'Customer requested plan upgrade on 2026-02-09',
        ]);

    $response->assertSessionHas('success', 'Service has been updated successfully.');

    // Verify audit log
    $auditLog = AuditLog::where('action', 'update_service')
        ->where('resource_id', $service->id)
        ->first();

    expect($auditLog)->not->toBeNull();
    expect($auditLog->changes)->toHaveKey('notes');
});

test('update returns info message when no changes made', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $response = $this->actingAs($this->admin)
        ->put("http://admin.ezscale.dev/services/{$service->id}", [
            'plan_id' => $this->plan->id, // Same plan
        ]);

    $response->assertSessionHas('info', 'No changes were made to the service.');
});

test('non-admin cannot provision service', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'subscription_id' => $this->subscription->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'pending',
        'provisioned_at' => null,
    ]);

    $response = $this->actingAs($this->customer)
        ->post("http://admin.ezscale.dev/services/{$service->id}/provision");

    $response->assertForbidden();
});

test('non-admin cannot modify service', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $newPlan = Plan::factory()->create([
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $response = $this->actingAs($this->customer)
        ->put("http://admin.ezscale.dev/services/{$service->id}", [
            'plan_id' => $newPlan->id,
        ]);

    $response->assertForbidden();
});

test('service show page includes available plans', function (): void {
    $service = Service::factory()->create([
        'user_id' => $this->customer->id,
        'plan_id' => $this->plan->id,
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $plan2 = Plan::factory()->create([
        'service_type' => 'vps',
        'status' => 'active',
    ]);

    $plan3 = Plan::factory()->create([
        'service_type' => 'dedicated',
        'status' => 'active',
    ]);

    $response = $this->actingAs($this->admin)
        ->get("http://admin.ezscale.dev/services/{$service->id}");

    $response->assertOk();

    // Should have availablePlans prop
    $props = $response->viewData('page')['props'];
    expect($props)->toHaveKey('availablePlans');

    // Should only include VPS plans (same service type)
    $availablePlans = collect($props['availablePlans']);
    expect($availablePlans)->toHaveCount(2);
    expect($availablePlans->pluck('id'))->toContain($this->plan->id, $plan2->id);
    expect($availablePlans->pluck('id'))->not->toContain($plan3->id);
});
