<?php

namespace Tests\Feature;

use App\Models\Company;
use App\Models\SubscriptionCoupon;
use App\Models\SubscriptionPlan;
use App\Models\User;
use App\Services\CouponService;
use App\Services\PlanPricingService;
use App\Services\TenantRoleService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Str;
use Tests\TestCase;

class YearlySubscriptionRedesignTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(\Database\Seeders\SubscriptionPlanSeeder::class);
    }

    public function test_free_plan_is_seeded_with_ten_seats(): void
    {
        $free = SubscriptionPlan::where('name', 'Free')->first();
        $this->assertNotNull($free);
        $this->assertSame(10, $free->employee_limit);
        $this->assertSame(0.0, (float) $free->base_price_yearly);
        $this->assertSame('yearly', $free->billing_type);
    }

    public function test_plan_discount_and_coupon_stacking(): void
    {
        $plan = SubscriptionPlan::where('name', 'Team 25')->firstOrFail();
        $plan->update(['discount_type' => 'percent', 'discount_value' => 10]);

        $coupon = SubscriptionCoupon::create([
            'code' => 'SAVE100',
            'discount_type' => 'fixed',
            'discount_value' => 100,
            'is_active' => true,
            'max_uses_per_company' => 5,
        ]);

        $company = Company::create([
            'name' => 'Acme '.Str::random(4),
            'short_name' => 'ACME',
            'email' => 'acme-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
        ]);

        $pricing = app(PlanPricingService::class);
        $quote = $pricing->calculateYearlyTotal($plan, 'SAVE100', $company);

        // 14999 - 10% = 13499.1, then -100 = 13399.1
        $this->assertEqualsWithDelta(1499.9, $quote['plan_discount_amount'], 0.01);
        $this->assertEqualsWithDelta(100.0, $quote['coupon_discount_amount'], 0.01);
        $this->assertEqualsWithDelta(13399.1, $quote['total'], 0.01);
        $this->assertSame($coupon->id, $quote['coupon']->id);
    }

    public function test_mid_cycle_upgrade_proration_keeps_math(): void
    {
        $old = SubscriptionPlan::where('name', 'Team 25')->firstOrFail();
        $new = SubscriptionPlan::where('name', 'Team 50')->firstOrFail();

        $company = Company::create([
            'name' => 'Upgrade Co',
            'short_name' => 'UPG',
            'email' => 'upg-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
            'subscription_plan_id' => $old->id,
            'licence_expire_on' => now()->addDays(179)->toDateString(),
        ]);

        $pricing = app(PlanPricingService::class);
        $this->assertTrue($pricing->canUpgradeTo($company, $new));

        $quote = $pricing->calculateUpgradeQuote($company, $new);
        $days = $quote['days_remaining'];
        $expectedGross = max(0, round(
            ($new->effectiveYearlyPrice() - $old->effectiveYearlyPrice()) * ($days / 365),
            2
        ));

        $this->assertTrue($quote['is_upgrade']);
        $this->assertEqualsWithDelta($expectedGross, $quote['upgrade_gross'], 0.05);
        $this->assertEqualsWithDelta($expectedGross, $quote['total'], 0.05);
    }

    public function test_is_subscription_active_ignores_expired_licence_until_suspended(): void
    {
        $company = Company::create([
            'name' => 'Warn Co',
            'short_name' => 'WRN',
            'email' => 'warn-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
            'licence_expire_on' => now()->subDay()->toDateString(),
        ]);

        $this->assertTrue($company->isSubscriptionActive());
        $this->assertTrue($company->isLicenceExpired());

        $company->update(['status' => 'suspended']);
        $this->assertFalse($company->fresh()->isSubscriptionActive());
    }

    public function test_seat_limit_blocks_employee_create(): void
    {
        $plan = SubscriptionPlan::where('name', 'Free')->firstOrFail();
        $company = Company::create([
            'name' => 'Seat Co',
            'short_name' => 'SEAT',
            'email' => 'seat-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
            'subscription_plan_id' => $plan->id,
            'licence_expire_on' => now()->addYear()->toDateString(),
        ]);
        TenantRoleService::syncAllDefaultRolesForCompany((int) $company->id);

        for ($i = 0; $i < 10; $i++) {
            User::factory()->create([
                'company_id' => $company->id,
                'user_type' => User::USER_TYPE_STAFF,
                'status' => User::STATUS_ACTIVE,
                'email' => 'staff'.$i.Str::lower(Str::random(4)).'@example.com',
            ]);
        }

        $this->assertFalse($company->fresh()->canAddEmployees(1));
        $this->assertSame(10, $company->fresh()->activeEmployeeCount());
    }

    public function test_null_plan_falls_back_to_free_seat_limit(): void
    {
        $company = Company::create([
            'name' => 'No Plan Co',
            'short_name' => 'NOPL',
            'email' => 'nopl-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
            'subscription_plan_id' => null,
        ]);

        $this->assertNull($company->subscription_plan_id);
        $this->assertSame(10, $company->employeeSeatLimit());
        $this->assertTrue($company->canAddEmployees(10));
        $this->assertFalse($company->canAddEmployees(11));
    }

    public function test_web_store_blocked_when_free_seat_limit_reached(): void
    {
        $this->withoutMiddleware();
        [$actor, $company, $work] = $this->createSeatLimitedActor(10);

        $email = 'overflow-'.Str::lower(Str::random(6)).'@example.com';
        $response = $this->actingAs($actor)->from(route('employees.create'))->post(route('employees.store'), array_merge($work, [
            'name' => 'Overflow User',
            'email' => $email,
            'password' => 'password123',
            'gender' => 'male',
        ]));

        $response->assertRedirect();
        $response->assertSessionHas('error');
        $this->assertStringContainsString('Employee seat limit reached', (string) session('error'));
        $this->assertSame(10, $company->fresh()->activeEmployeeCount());
        $this->assertDatabaseMissing('users', ['email' => $email]);
    }

    public function test_create_form_redirects_when_seat_limit_reached(): void
    {
        $this->withoutMiddleware();
        [$actor] = $this->createSeatLimitedActor(10);

        $response = $this->actingAs($actor)->get(route('employees.create'));

        $response->assertRedirect(route('employees.index'));
        $response->assertSessionHas('error');
        $this->assertStringContainsString('Employee seat limit reached', (string) session('error'));
    }

    public function test_reactivation_blocked_when_seat_limit_reached(): void
    {
        $this->withoutMiddleware();
        [$actor, $company, $work] = $this->createSeatLimitedActor(9);

        $inactive = User::factory()->create([
            'company_id' => $company->id,
            'user_type' => User::USER_TYPE_STAFF,
            'status' => User::STATUS_INACTIVE,
            'email' => 'inactive-'.Str::lower(Str::random(6)).'@example.com',
            'designation_id' => $work['designation_id'],
            'location_id' => $work['location_id'],
            'shift_id' => $work['shift_id'],
            'report_to' => $work['report_to'],
            'gender' => 'female',
        ]);

        // Fill remaining seat so reactivation would exceed limit.
        User::factory()->create([
            'company_id' => $company->id,
            'user_type' => User::USER_TYPE_STAFF,
            'status' => User::STATUS_ACTIVE,
            'email' => 'filler-'.Str::lower(Str::random(6)).'@example.com',
        ]);

        $this->assertSame(10, $company->fresh()->activeEmployeeCount());
        $this->assertFalse($company->fresh()->canAddEmployees(1));

        $response = $this->actingAs($actor)->from(route('employees.edit', $inactive))->put(route('employees.update', $inactive), array_merge($work, [
            'name' => $inactive->name,
            'email' => $inactive->email,
            'gender' => 'female',
            'status' => User::STATUS_ACTIVE,
            'allow_login' => 1,
        ]));

        $response->assertRedirect();
        $response->assertSessionHas('error');
        $this->assertStringContainsString('Employee seat limit reached', (string) session('error'));
        $this->assertSame(User::STATUS_INACTIVE, $inactive->fresh()->status);
        $this->assertSame(10, $company->fresh()->activeEmployeeCount());
    }

    /**
     * @return array{0: User, 1: Company, 2: array<string, mixed>}
     */
    private function createSeatLimitedActor(int $activeStaffCount): array
    {
        $plan = SubscriptionPlan::where('name', 'Free')->firstOrFail();
        $company = Company::create([
            'name' => 'HTTP Seat Co',
            'short_name' => 'HSEA',
            'email' => 'hsea-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
            'subscription_plan_id' => $plan->id,
            'licence_expire_on' => now()->addYear()->toDateString(),
        ]);
        TenantRoleService::syncAllDefaultRolesForCompany((int) $company->id);

        $adminRole = \App\Models\Role::withoutGlobalScopes()
            ->where('company_id', $company->id)
            ->where('name', 'admin')
            ->firstOrFail();

        $actor = User::factory()->create([
            'company_id' => $company->id,
            'role_id' => $adminRole->id,
            'user_type' => User::USER_TYPE_ADMIN,
            'status' => User::STATUS_ACTIVE,
            'is_superadmin' => false,
            'email' => 'admin-'.Str::lower(Str::random(6)).'@example.com',
        ]);

        $designation = \App\Models\Designation::withoutGlobalScopes()->create([
            'company_id' => $company->id,
            'name' => 'Engineer',
        ]);
        $location = \App\Models\Location::withoutGlobalScopes()->create([
            'company_id' => $company->id,
            'name' => 'HQ',
        ]);
        $shift = \App\Models\Shift::withoutGlobalScopes()->create([
            'company_id' => $company->id,
            'name' => 'General',
            'clock_in_time' => '09:00',
            'clock_out_time' => '18:00',
            'is_active' => true,
        ]);

        for ($i = 0; $i < $activeStaffCount; $i++) {
            User::factory()->create([
                'company_id' => $company->id,
                'user_type' => User::USER_TYPE_STAFF,
                'status' => User::STATUS_ACTIVE,
                'email' => 'staff'.$i.Str::lower(Str::random(4)).'@example.com',
            ]);
        }

        return [$actor, $company, [
            'designation_id' => $designation->id,
            'location_id' => $location->id,
            'shift_id' => $shift->id,
            'report_to' => $actor->id,
            'role_id' => $adminRole->id,
        ]];
    }

    public function test_suspend_expired_command_sets_status(): void
    {
        $company = Company::create([
            'name' => 'Expire Co',
            'short_name' => 'EXP',
            'email' => 'exp-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
            'licence_expire_on' => now()->subDays(2)->toDateString(),
        ]);

        Artisan::call('subscriptions:suspend-expired');

        $this->assertSame('suspended', $company->fresh()->status);
    }

    public function test_invalid_coupon_rejected(): void
    {
        $plan = SubscriptionPlan::where('name', 'Team 25')->firstOrFail();
        $company = Company::create([
            'name' => 'Coupon Co',
            'short_name' => 'CPN',
            'email' => 'cpn-'.Str::lower(Str::random(6)).'@example.com',
            'status' => 'active',
        ]);

        $this->expectException(\Illuminate\Validation\ValidationException::class);
        app(CouponService::class)->validate('NOPE', $company, $plan, 1000);
    }
}
