<?php
namespace Tests\Feature\Accounting;
use App\Enums\AccountType;use App\Enums\DocumentStatus;use App\Enums\JournalStatus;use App\Enums\NormalBalance;use App\Enums\PartyRoleType;use App\Models\Account;use App\Models\AccountingPeriod;use App\Models\Branch;use App\Models\Company;use App\Models\ContraSettlement;use App\Models\CustomerInvoice;use App\Models\FiscalYear;use App\Models\JournalEntry;use App\Models\Party;use App\Models\Payment;use App\Models\Receipt;use App\Models\VendorBill;use App\Services\Accounting\ContraSettlementPostingService;use App\Services\Accounting\InvoicePostingService;use App\Services\Accounting\LedgerBalanceService;use App\Services\Accounting\PaymentPostingService;use App\Services\Accounting\ReceiptPostingService;use App\Services\Accounting\VendorBillPostingService;use Illuminate\Foundation\Testing\RefreshDatabase;use Tests\TestCase;
final class DualRolePartyScenarioTest extends TestCase
{
    use RefreshDatabase;
    public function test_customer_and_vendor_ledgers_remain_separate_until_authorised_contra(): void
    {
        $company=Company::create(['legal_name'=>'Test Transport LLC','base_currency'=>'AED','timezone'=>'Asia/Dubai','vat_registered'=>true,'is_active'=>true]);$branch=Branch::create(['company_id'=>$company->id,'code'=>'HO','name'=>'Head Office','is_active'=>true]);$fy=FiscalYear::create(['company_id'=>$company->id,'name'=>'2026','starts_on'=>'2026-01-01','ends_on'=>'2026-12-31','status'=>'open']);AccountingPeriod::create(['fiscal_year_id'=>$fy->id,'name'=>'July 2026','starts_on'=>'2026-07-01','ends_on'=>'2026-07-31','is_locked'=>false]);
        $cash=$this->account($company->id,'1000','Bank',AccountType::ASSET,NormalBalance::DEBIT,'cash');$ar=$this->account($company->id,'1100','AR',AccountType::ASSET,NormalBalance::DEBIT,'ar');$inputVat=$this->account($company->id,'1300','Input VAT',AccountType::ASSET,NormalBalance::DEBIT,'input_vat');$ap=$this->account($company->id,'2000','AP',AccountType::LIABILITY,NormalBalance::CREDIT,'ap');$outputVat=$this->account($company->id,'2200','Output VAT',AccountType::LIABILITY,NormalBalance::CREDIT,'output_vat');$income=$this->account($company->id,'4000','Transport Income',AccountType::INCOME,NormalBalance::CREDIT,'income');$cost=$this->account($company->id,'5000','Vendor Transport Cost',AccountType::COST_OF_REVENUE,NormalBalance::DEBIT,'cost');
        $party=Party::create(['company_id'=>$company->id,'assigned_branch_id'=>$branch->id,'legal_name'=>'Dual Role Logistics LLC','entity_type'=>'company','country_code'=>'AE','default_currency'=>'AED','credit_limit'=>'50000.0000','credit_period_days'=>30,'is_active'=>true]);$party->roles()->createMany([['role'=>PartyRoleType::CUSTOMER],['role'=>PartyRoleType::VENDOR]]);
        $invoice=CustomerInvoice::create(['company_id'=>$company->id,'branch_id'=>$branch->id,'party_id'=>$party->id,'invoice_number'=>'HO-INV-2026-000001','invoice_type'=>'tax_invoice','issue_date'=>'2026-07-01','supply_date'=>'2026-07-01','due_date'=>'2026-07-31','currency_code'=>'AED','exchange_rate'=>'1','subtotal'=>'10000','discount_total'=>'0','taxable_total'=>'10000','vat_total'=>'500','grand_total'=>'10500','receivable_account_id'=>$ar->id,'income_account_id'=>$income->id,'output_vat_account_id'=>$outputVat->id,'status'=>DocumentStatus::APPROVED]);app(InvoicePostingService::class)->post($invoice);
        $receipt=Receipt::create(['company_id'=>$company->id,'branch_id'=>$branch->id,'party_id'=>$party->id,'receipt_number'=>'HO-RV-2026-000001','receipt_date'=>'2026-07-02','amount'=>'4000','currency_code'=>'AED','exchange_rate'=>'1','payment_method'=>'bank_transfer','cash_bank_account_id'=>$cash->id,'receivable_account_id'=>$ar->id,'reference_number'=>'BANK-1','status'=>DocumentStatus::APPROVED]);app(ReceiptPostingService::class)->post($receipt,[$invoice->id=>'4000']);
        $bill=VendorBill::create(['company_id'=>$company->id,'branch_id'=>$branch->id,'party_id'=>$party->id,'bill_number'=>'HO-VB-2026-000001','vendor_invoice_number'=>'V-001','invoice_date'=>'2026-07-03','supply_date'=>'2026-07-03','due_date'=>'2026-08-02','currency_code'=>'AED','exchange_rate'=>'1','taxable_total'=>'3000','recoverable_vat_total'=>'150','nonrecoverable_vat_total'=>'0','grand_total'=>'3150','payable_account_id'=>$ap->id,'expense_account_id'=>$cost->id,'input_vat_account_id'=>$inputVat->id,'status'=>DocumentStatus::APPROVED]);app(VendorBillPostingService::class)->post($bill);
        $payment=Payment::create(['company_id'=>$company->id,'branch_id'=>$branch->id,'party_id'=>$party->id,'payment_number'=>'HO-PV-2026-000001','payment_date'=>'2026-07-04','amount'=>'1000','currency_code'=>'AED','exchange_rate'=>'1','payment_method'=>'bank_transfer','cash_bank_account_id'=>$cash->id,'payable_account_id'=>$ap->id,'reference_number'=>'BANK-2','status'=>DocumentStatus::APPROVED]);app(PaymentPostingService::class)->post($payment,[$bill->id=>'1000']);
        $balances=app(LedgerBalanceService::class);self::assertSame('6500.0000',$balances->customerReceivable($party->id));self::assertSame('2150.0000',$balances->vendorPayable($party->id));
        $contra=ContraSettlement::create(['company_id'=>$company->id,'branch_id'=>$branch->id,'party_id'=>$party->id,'contra_number'=>'HO-CV-2026-000001','settlement_date'=>'2026-07-05','amount'=>'2000','customer_invoice_id'=>$invoice->id,'vendor_bill_id'=>$bill->id,'receivable_account_id'=>$ar->id,'payable_account_id'=>$ap->id,'reason'=>'Approved contractual offset','status'=>DocumentStatus::APPROVED]);app(ContraSettlementPostingService::class)->post($contra);
        self::assertSame(['receivable'=>'4500.0000','payable'=>'150.0000','net_exposure'=>'4350.0000'],$balances->combinedPartyPosition($party->id));
        JournalEntry::query()->where('status',JournalStatus::POSTED->value)->with('lines')->each(function(JournalEntry $journal): void { self::assertGreaterThanOrEqual(2,$journal->lines->count());self::assertSame($journal->debit_total,$journal->credit_total);self::assertSame($journal->debit_total,$this->sumDecimal($journal->lines->pluck('debit')->all()));self::assertSame($journal->credit_total,$this->sumDecimal($journal->lines->pluck('credit')->all()));});
        self::assertSame('500.0000',$this->sumDecimal($outputVat->journalLines()->pluck('credit')->all()));self::assertSame('150.0000',$this->sumDecimal($inputVat->journalLines()->pluck('debit')->all()));
    }
    /** @param array<int, mixed> $values */
    private function sumDecimal(array $values): string
    {
        $total = \App\Support\Decimal::scale('0');
        foreach ($values as $value) {
            $total = \App\Support\Decimal::add($total, (string) $value);
        }
        return $total;
    }

    private function account(int $companyId,string $code,string $name,AccountType $type,NormalBalance $normal,string $key): Account{return Account::create(['company_id'=>$companyId,'code'=>$code,'name'=>$name,'type'=>$type,'normal_balance'=>$normal,'is_control_account'=>in_array($key,['ar','ap'],true),'is_active'=>true,'system_key'=>$key]);}
}
