<?php

namespace Tests\Feature;

use App\Enums\ChargeStatus;
use App\Models\BillingPeriod;
use App\Models\Customer;
use App\Models\Line;
use App\Models\LineBillingCharge;
use App\Models\Plan;
use App\Models\TelecomCompany;
use App\Services\BillingService;
use App\Services\LineSaleService;
use App\Services\PaymentService;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;

class BillingPaymentTest extends TestCase
{
    protected function soldLine(): array
    {
        $admin = $this->admin();
        $company = TelecomCompany::factory()->create();
        $plan = Plan::factory()->create(['telecom_company_id' => $company->id, 'price' => '100.00']);
        $line = Line::factory()->create(['telecom_company_id' => $company->id, 'sale_price' => '0.00']);
        $customer = Customer::factory()->create();
        app(LineSaleService::class)->sell([
            'customer_id' => $customer->id,
            'line_id' => $line->id,
            'plan_id' => $plan->id,
            'line_price' => '0',
            'paid' => '0.00',
            'payment_method' => 'cash',
            'sale_date' => now()->toDateString(),
            'activation_date' => now()->startOfMonth()->subMonths(2)->toDateString(),
        ], $admin);

        return [$admin, $line->fresh(), $plan];
    }

    public function test_multiple_month_payment_and_allocations(): void
    {
        [$admin, $line] = $this->soldLine();
        $billing = app(BillingService::class);
        $july = $billing->ensurePeriod(2026, 7);
        $august = $billing->ensurePeriod(2026, 8);

        $result = app(PaymentService::class)->collectBilling($line, [$july->id, $august->id], $admin, [
            'payment_method' => 'cash',
        ]);

        $this->assertEquals(2, $result['payment']->allocations()->count());
        $this->assertDatabaseHas('line_billing_charges', [
            'line_id' => $line->id,
            'billing_period_id' => $july->id,
            'status' => ChargeStatus::Paid->value,
        ]);
        $this->assertTrue(bccomp((string) $result['invoice']->total, '200.00', 2) === 0);
        $this->assertDatabaseHas('cash_transactions', ['reference_id' => $result['payment']->id]);
    }

    public function test_duplicate_billing_is_prevented(): void
    {
        [$admin, $line] = $this->soldLine();
        $period = app(BillingService::class)->ensurePeriod(2026, 7);
        app(PaymentService::class)->collectBilling($line, [$period->id], $admin, ['payment_method' => 'cash']);

        $this->expectException(ValidationException::class);
        app(PaymentService::class)->collectBilling($line, [$period->id], $admin, ['payment_method' => 'cash']);
    }

    public function test_unique_line_period_constraint(): void
    {
        $line = Line::factory()->create();
        $period = BillingPeriod::findOrCreatePeriod(2026, 5);
        LineBillingCharge::query()->create([
            'line_id' => $line->id,
            'billing_period_id' => $period->id,
            'amount' => '100.00',
            'status' => 'unpaid',
        ]);

        $this->expectException(\Illuminate\Database\QueryException::class);
        LineBillingCharge::query()->create([
            'line_id' => $line->id,
            'billing_period_id' => $period->id,
            'amount' => '100.00',
            'status' => 'unpaid',
        ]);
    }

    public function test_overdue_includes_unpaid_period(): void
    {
        [$admin, $line] = $this->soldLine();
        $period = app(BillingService::class)->ensurePeriod((int) now()->year, (int) now()->month);
        [$query] = app(BillingService::class)->overdueQuery(['year' => $period->year, 'month' => $period->month, 'line_id' => $line->id]);
        $this->assertTrue($query->where('id', $line->id)->exists());
    }

    public function test_historical_period_price_is_stored_on_invoice(): void
    {
        [$admin, $line, $plan] = $this->soldLine();
        $plan->update(['price' => '120.00']);
        $plan->priceHistory()->create([
            'old_price' => '100.00',
            'new_price' => '120.00',
            'effective_date' => '2026-08-01',
            'user_id' => $admin->id,
        ]);

        $july = app(BillingService::class)->ensurePeriod(2026, 7);
        $august = app(BillingService::class)->ensurePeriod(2026, 8);
        $result = app(PaymentService::class)->collectBilling($line->fresh('currentPlan'), [$july->id, $august->id], $admin, ['payment_method' => 'cash']);
        $items = $result['invoice']->items()->orderBy('id')->get();
        $this->assertEquals('100.00', $items[0]->unit_price);
        $this->assertEquals('120.00', $items[1]->unit_price);
    }

    public function test_collection_amount_can_be_changed(): void
    {
        [$admin, $line] = $this->soldLine();
        $july = app(BillingService::class)->ensurePeriod(2026, 7);

        $result = app(PaymentService::class)->collectBilling($line, [$july->id], $admin, [
            'payment_method' => 'cash',
            'amounts' => [$july->id => '150.00'],
        ]);

        $this->assertTrue(bccomp((string) $result['invoice']->total, '150.00', 2) === 0);
        $this->assertDatabaseHas('line_billing_charges', [
            'line_id' => $line->id,
            'billing_period_id' => $july->id,
            'amount' => '150.00',
            'status' => ChargeStatus::Paid->value,
        ]);
    }

    public function test_plan_create_page_loads(): void
    {
        $admin = $this->admin();
        TelecomCompany::factory()->create();

        $this->actingAs($admin)->get(route('plans.create'))->assertOk();
    }

    public function test_billing_search_shows_line_snapshot(): void
    {
        [$admin, $line] = $this->soldLine();

        $this->actingAs($admin)
            ->get(route('billing.index', ['q' => $line->phone_number]))
            ->assertOk()
            ->assertSee($line->phone_number);
    }

    public function test_invoice_print_and_pdf(): void
    {
        [$admin, $line] = $this->soldLine();
        $july = app(BillingService::class)->ensurePeriod(2026, 7);
        $result = app(PaymentService::class)->collectBilling($line, [$july->id], $admin, [
            'payment_method' => 'cash',
        ]);

        app()->setLocale('ar');

        $print = $this->actingAs($admin)->get(route('invoices.print', $result['invoice']));
        $print->assertOk()->assertSee($line->phone_number);

        $pdf = $this->actingAs($admin)->get(route('invoices.pdf', $result['invoice']));
        $pdf->assertOk()->assertHeader('content-type', 'application/pdf');
        $this->assertStringStartsWith('%PDF', $pdf->getContent());
        $this->assertGreaterThan(2000, strlen($pdf->getContent()));
        $this->assertStringNotContainsString('????', $pdf->getContent());
    }

    public function test_package_year_allows_advance_months(): void
    {
        $admin = $this->admin();
        $company = TelecomCompany::factory()->create();
        $plan = Plan::factory()->create(['telecom_company_id' => $company->id, 'price' => '35.00']);
        $line = Line::factory()->create(['telecom_company_id' => $company->id, 'sale_price' => '150.00']);
        $customer = Customer::factory()->create();
        app(LineSaleService::class)->sell([
            'customer_id' => $customer->id,
            'line_id' => $line->id,
            'plan_id' => $plan->id,
            'payment_method' => 'cash',
            'sale_date' => now()->toDateString(),
            'activation_date' => now()->toDateString(),
        ], $admin);

        $year = app(BillingService::class)->subscriptionYear($line->fresh(['charges', 'sales', 'currentPlan']));
        $this->assertCount(12, $year['remaining']);
        $this->assertCount(0, $year['paid']);
        $this->assertEquals(now()->month, $year['remaining'][0]['period']->month);

        $firstThree = array_map(fn ($row) => $row['period']->id, array_slice($year['remaining'], 0, 3));
        $result = app(PaymentService::class)->collectBilling($line->fresh(), $firstThree, $admin, ['payment_method' => 'cash']);
        $this->assertEquals(3, $result['payment']->allocations()->count());
        $this->assertTrue(bccomp((string) $result['invoice']->total, '105.00', 2) === 0);

        $after = app(BillingService::class)->subscriptionYear($line->fresh(['charges', 'sales', 'currentPlan']));
        $this->assertCount(3, $after['paid']);
        $this->assertCount(9, $after['remaining']);
    }

    public function test_new_package_year_opens_after_twelve_months_are_paid(): void
    {
        $admin = $this->admin();
        $company = TelecomCompany::factory()->create();
        $plan = Plan::factory()->create(['telecom_company_id' => $company->id, 'price' => '35.00']);
        $line = Line::factory()->create(['telecom_company_id' => $company->id, 'sale_price' => '150.00']);
        $customer = Customer::factory()->create();
        app(LineSaleService::class)->sell([
            'customer_id' => $customer->id,
            'line_id' => $line->id,
            'plan_id' => $plan->id,
            'payment_method' => 'cash',
            'sale_date' => now()->toDateString(),
            'activation_date' => now()->toDateString(),
        ], $admin);

        $billing = app(BillingService::class);
        $year = $billing->subscriptionYear($line->fresh(['charges', 'sales', 'currentPlan']));
        $periodIds = array_map(fn ($row) => $row['period']->id, $year['remaining']);
        app(PaymentService::class)->collectBilling($line->fresh(), $periodIds, $admin, ['payment_method' => 'cash']);

        $next = $billing->subscriptionYear($line->fresh(['charges', 'sales', 'currentPlan']));
        $this->assertEquals(2, $next['year_number']);
        $this->assertCount(12, $next['remaining']);
        $this->assertCount(0, $next['paid']);
        $this->assertTrue($next['start']->equalTo($year['start']->copy()->addYear()));
    }
}
