<?php

declare(strict_types=1);

use App\Models\Invoice;
use App\Models\Service;
use App\Models\User;
use Database\Seeders\RoleAndPermissionSeeder;
use Laravel\Passport\Passport;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;

beforeEach(function (): void {
    Passport::$validateKeyPermissions = false;
    $this->seed(RoleAndPermissionSeeder::class);

    // Create roles and permissions for the 'api' guard so Passport auth works with Spatie
    app()[PermissionRegistrar::class]->forgetCachedPermissions();

    $apiAdmin = Role::findOrCreate('admin', 'api');
    Role::findOrCreate('customer', 'api');

    $permissions = Permission::where('guard_name', 'web')->pluck('name');
    foreach ($permissions as $permissionName) {
        $perm = Permission::findOrCreate($permissionName, 'api');
        $apiAdmin->givePermissionTo($perm);
    }
});

/**
 * Create an admin user with the 'api' guard role and authenticate via Passport.
 */
function createApiAdmin(): User
{
    $admin = User::factory()->create();
    $admin->assignRole(Role::findByName('admin', 'api'));
    Passport::actingAs($admin);

    return $admin;
}

/**
 * Create a customer user with the 'api' guard role.
 */
function createApiCustomer(array $attributes = []): User
{
    $customer = User::factory()->create($attributes);
    $customer->assignRole(Role::findByName('customer', 'api'));

    return $customer;
}

// ---------------------------------------------------------------------------
// Authentication & Authorization
// ---------------------------------------------------------------------------
describe('Authentication & Authorization', function (): void {
    it('returns 401 for unauthenticated requests', function (): void {
        $this->getJson('/api/v1/admin/customers')
            ->assertUnauthorized();
    });

    it('returns 403 for non-admin users accessing admin endpoints', function (): void {
        $customer = createApiCustomer();
        Passport::actingAs($customer);

        $this->getJson('/api/v1/admin/customers')
            ->assertForbidden();
    });

    it('returns 403 for non-admin accessing analytics', function (): void {
        $customer = createApiCustomer();
        Passport::actingAs($customer);

        $this->getJson('/api/v1/admin/analytics')
            ->assertForbidden();
    });

    it('returns 403 for non-admin trying to suspend a service', function (): void {
        $customer = createApiCustomer();
        Passport::actingAs($customer);

        $service = Service::factory()->create();

        $this->postJson("/api/v1/admin/services/{$service->id}/suspend")
            ->assertForbidden();
    });
});

// ---------------------------------------------------------------------------
// Customer Management
// ---------------------------------------------------------------------------
describe('Customer Management', function (): void {
    it('allows admin to list customers', function (): void {
        $admin = createApiAdmin();

        // Create customers with the 'web' guard role (used by User::role('customer') query)
        User::factory()->customer()->count(5)->create();

        $response = $this->getJson('/api/v1/admin/customers');

        $response->assertOk()
            ->assertJsonStructure([
                'data' => [
                    '*' => ['id', 'name', 'email', 'status', 'services_count', 'subscriptions_count', 'created_at'],
                ],
                'links',
                'meta',
            ]);

        expect($response->json('meta.total'))->toBe(5);
    });

    it('allows admin to search customers by name', function (): void {
        createApiAdmin();

        User::factory()->customer()->create(['name' => 'John Searchable']);
        User::factory()->customer()->create(['name' => 'Jane Other']);

        $response = $this->getJson('/api/v1/admin/customers?search=Searchable');

        $response->assertOk();
        expect($response->json('meta.total'))->toBe(1);
        expect($response->json('data.0.name'))->toBe('John Searchable');
    });

    it('allows admin to search customers by email', function (): void {
        createApiAdmin();

        User::factory()->customer()->create(['email' => 'unique-test@example.com']);
        User::factory()->customer()->create(['email' => 'other@example.com']);

        $response = $this->getJson('/api/v1/admin/customers?search=unique-test');

        $response->assertOk();
        expect($response->json('meta.total'))->toBe(1);
        expect($response->json('data.0.email'))->toBe('unique-test@example.com');
    });

    it('allows admin to filter customers by status', function (): void {
        createApiAdmin();

        User::factory()->customer()->count(3)->create(['status' => 'active']);
        User::factory()->customer()->suspended()->count(2)->create();

        $response = $this->getJson('/api/v1/admin/customers?status=suspended');

        $response->assertOk();
        expect($response->json('meta.total'))->toBe(2);
    });

    it('allows admin to view customer details', function (): void {
        createApiAdmin();

        $customer = User::factory()->customer()->create();
        Service::factory()->count(3)->create(['user_id' => $customer->id]);

        $response = $this->getJson("/api/v1/admin/customers/{$customer->id}");

        $response->assertOk()
            ->assertJsonStructure([
                'data' => ['id', 'name', 'email', 'status', 'services_count', 'subscriptions_count', 'created_at'],
            ]);

        expect($response->json('data.id'))->toBe($customer->id);
        expect($response->json('data.services_count'))->toBe(3);
    });

    it('paginates customers with custom per_page', function (): void {
        createApiAdmin();

        User::factory()->customer()->count(10)->create();

        $response = $this->getJson('/api/v1/admin/customers?per_page=5');

        $response->assertOk();
        expect($response->json('meta.per_page'))->toBe(5);
        expect(count($response->json('data')))->toBe(5);
    });

    it('creates an audit log when listing customers', function (): void {
        $admin = createApiAdmin();

        $this->getJson('/api/v1/admin/customers')
            ->assertOk();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'api_list_customers',
            'resource_type' => 'user',
        ]);
    });

    it('creates an audit log when viewing a customer', function (): void {
        $admin = createApiAdmin();

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

        $this->getJson("/api/v1/admin/customers/{$customer->id}")
            ->assertOk();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'api_view_customer',
            'resource_type' => 'user',
            'resource_id' => $customer->id,
        ]);
    });
});

// ---------------------------------------------------------------------------
// Service Management
// ---------------------------------------------------------------------------
describe('Service Management', function (): void {
    it('allows admin to list all services', function (): void {
        createApiAdmin();

        Service::factory()->count(5)->create();

        $response = $this->getJson('/api/v1/admin/services');

        $response->assertOk()
            ->assertJsonStructure([
                'data' => [
                    '*' => ['id', 'user', 'service_type', 'platform', 'status', 'hostname', 'ipv4_address', 'plan', 'created_at'],
                ],
                'links',
                'meta',
            ]);

        expect($response->json('meta.total'))->toBe(5);
    });

    it('allows admin to filter services by status', function (): void {
        createApiAdmin();

        Service::factory()->count(3)->create(['status' => 'active']);
        Service::factory()->suspended()->count(2)->create();

        $response = $this->getJson('/api/v1/admin/services?status=suspended');

        $response->assertOk();
        expect($response->json('meta.total'))->toBe(2);
    });

    it('allows admin to filter services by service_type', function (): void {
        createApiAdmin();

        Service::factory()->count(2)->create(['service_type' => 'vps', 'platform' => 'virtfusion']);
        Service::factory()->count(3)->create(['service_type' => 'dedicated', 'platform' => 'synergycp']);

        $response = $this->getJson('/api/v1/admin/services?service_type=vps');

        $response->assertOk();
        expect($response->json('meta.total'))->toBe(2);
    });

    it('allows admin to search services by customer name', function (): void {
        createApiAdmin();

        $customer = User::factory()->customer()->create(['name' => 'SearchTarget User']);
        Service::factory()->create(['user_id' => $customer->id]);
        Service::factory()->count(3)->create();

        $response = $this->getJson('/api/v1/admin/services?search=SearchTarget');

        $response->assertOk();
        expect($response->json('meta.total'))->toBe(1);
    });

    it('allows admin to view service details', function (): void {
        createApiAdmin();

        $service = Service::factory()->create();

        $response = $this->getJson("/api/v1/admin/services/{$service->id}");

        $response->assertOk()
            ->assertJsonStructure([
                'data' => ['id', 'user', 'service_type', 'platform', 'status', 'hostname', 'ipv4_address', 'plan', 'created_at'],
            ]);

        expect($response->json('data.id'))->toBe($service->id);
    });

    it('allows admin to suspend a service', function (): void {
        $admin = createApiAdmin();

        $service = Service::factory()->create(['status' => 'active']);

        $response = $this->postJson("/api/v1/admin/services/{$service->id}/suspend");

        $response->assertOk()
            ->assertJson(['message' => 'Service has been suspended.']);

        $service->refresh();
        expect($service->status)->toBe('suspended');
        expect($service->suspended_at)->not->toBeNull();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'api_suspend_service',
            'resource_type' => 'service',
            'resource_id' => $service->id,
        ]);
    });

    it('returns 422 when suspending an already suspended service', function (): void {
        createApiAdmin();

        $service = Service::factory()->suspended()->create();

        $response = $this->postJson("/api/v1/admin/services/{$service->id}/suspend");

        $response->assertUnprocessable()
            ->assertJson(['message' => 'Service is already suspended.']);
    });

    it('allows admin to unsuspend a service', function (): void {
        $admin = createApiAdmin();

        $service = Service::factory()->suspended()->create();

        $response = $this->postJson("/api/v1/admin/services/{$service->id}/unsuspend");

        $response->assertOk()
            ->assertJson(['message' => 'Service has been unsuspended.']);

        $service->refresh();
        expect($service->status)->toBe('active');
        expect($service->suspended_at)->toBeNull();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'api_unsuspend_service',
            'resource_type' => 'service',
            'resource_id' => $service->id,
        ]);
    });

    it('returns 422 when unsuspending a non-suspended service', function (): void {
        createApiAdmin();

        $service = Service::factory()->create(['status' => 'active']);

        $response = $this->postJson("/api/v1/admin/services/{$service->id}/unsuspend");

        $response->assertUnprocessable()
            ->assertJson(['message' => 'Service is not suspended.']);
    });

    it('creates an audit log when listing services', function (): void {
        $admin = createApiAdmin();

        $this->getJson('/api/v1/admin/services')
            ->assertOk();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'api_list_services',
            'resource_type' => 'service',
        ]);
    });
});

// ---------------------------------------------------------------------------
// Analytics
// ---------------------------------------------------------------------------
describe('Analytics', function (): void {
    it('allows admin to view analytics', function (): void {
        createApiAdmin();

        // Create some data for analytics — customers must have 'web' guard role
        // for User::role('customer') queries in AnalyticsController
        User::factory()->customer()->count(3)->create();
        Service::factory()->count(2)->create(['status' => 'active']);
        Invoice::factory()->count(2)->create(['status' => 'paid', 'total' => 50.00, 'paid_at' => now()]);
        Invoice::factory()->create(['status' => 'pending', 'total' => 25.00]);
        Invoice::factory()->create(['status' => 'overdue', 'total' => 30.00]);

        $response = $this->getJson('/api/v1/admin/analytics');

        $response->assertOk()
            ->assertJsonStructure([
                'data' => [
                    'total_customers',
                    'new_customers_this_month',
                    'mrr',
                    'arr',
                    'total_revenue',
                    'revenue_this_month',
                    'active_services',
                    'pending_invoices' => ['count', 'amount'],
                    'overdue_invoices' => ['count', 'amount'],
                    'revenue_by_month',
                    'customer_growth',
                    'churn_data',
                    'revenue_by_service_type',
                ],
            ]);

        expect($response->json('data.total_customers'))->toBe(3);
        expect($response->json('data.active_services'))->toBe(2);
        expect((float) $response->json('data.total_revenue'))->toBe(100.0);
        expect($response->json('data.pending_invoices.count'))->toBe(1);
        expect((float) $response->json('data.pending_invoices.amount'))->toBe(25.0);
        expect($response->json('data.overdue_invoices.count'))->toBe(1);
        expect((float) $response->json('data.overdue_invoices.amount'))->toBe(30.0);
    });

    it('creates an audit log when viewing analytics', function (): void {
        $admin = createApiAdmin();

        $this->getJson('/api/v1/admin/analytics')
            ->assertOk();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'api_view_analytics',
            'resource_type' => 'analytics',
        ]);
    });

    it('returns churn data with correct structure', function (): void {
        createApiAdmin();

        $response = $this->getJson('/api/v1/admin/analytics');

        $response->assertOk();

        $churnData = $response->json('data.churn_data');
        expect($churnData)->toBeArray();
        expect(count($churnData))->toBe(6);

        foreach ($churnData as $month) {
            expect($month)->toHaveKeys(['month', 'rate', 'cancelled']);
        }
    });
});
