<?php

namespace App\Core\Services\Accounting;

use App\Core\Repositories\ChartOfAccountRepository;
use App\Core\Repositories\JournalEntryRepository;
use App\Models\JournalEntry;
use App\Models\JournalEntryLine;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Journal Entry Service
 * Handles journal entry business logic with double-entry accounting
 */
class JournalEntryService
{
    public function __construct(
        private JournalEntryRepository $journalRepository,
        private ChartOfAccountRepository $accountRepository
    ) {}

    /**
     * Create a new journal entry
     */
    public function create(array $data): JournalEntry
    {
        DB::beginTransaction();

        try {
            // Generate entry number
            $data['entry_number'] = $this->journalRepository->generateEntryNumber();

            // Calculate totals
            $totals = $this->calculateTotals($data['lines'] ?? []);

            // Validate balance
            if (! $this->isBalanced($totals['debit'], $totals['credit'])) {
                throw new Exception('Journal entry must be balanced. Debits must equal credits.');
            }

            // Create journal entry
            $entry = $this->journalRepository->create([
                'entry_number' => $data['entry_number'],
                'entry_date' => $data['entry_date'],
                'description' => $data['description'] ?? null,
                'status' => $data['status'] ?? 'draft',
                'reference_type' => $data['reference_type'] ?? null,
                'reference_id' => $data['reference_id'] ?? null,
                'total_debit' => $totals['debit'],
                'total_credit' => $totals['credit'],
                'created_by' => auth()->id(),
            ]);

            // Create journal entry lines
            if (isset($data['lines']) && is_array($data['lines'])) {
                $this->createJournalLines($entry, $data['lines']);
            }

            // If status is posted, update account balances
            if ($entry->status === 'posted') {
                $this->postEntry($entry);
            }

            DB::commit();

            return $entry->load(['lines.account']);
        } catch (Exception $e) {
            DB::rollBack();
            Log::error('Journal entry creation failed: '.$e->getMessage());
            throw $e;
        }
    }

    /**
     * Post a journal entry (update account balances)
     */
    public function post(JournalEntry $entry): bool
    {
        if ($entry->status === 'posted') {
            throw new Exception('Journal entry is already posted.');
        }

        DB::beginTransaction();

        try {
            $this->postEntry($entry);

            $this->journalRepository->update($entry, [
                'status' => 'posted',
            ]);

            DB::commit();

            return true;
        } catch (Exception $e) {
            DB::rollBack();
            Log::error('Journal entry posting failed: '.$e->getMessage());
            throw $e;
        }
    }

    /**
     * Reverse a journal entry
     */
    public function reverse(JournalEntry $entry): JournalEntry
    {
        if ($entry->status !== 'posted') {
            throw new Exception('Only posted entries can be reversed.');
        }

        if ($entry->is_reversed) {
            throw new Exception('Entry is already reversed.');
        }

        DB::beginTransaction();

        try {
            // Create reversing entry
            $reversingData = [
                'entry_date' => now(),
                'description' => 'Reversal of '.$entry->entry_number,
                'status' => 'posted',
                'reference_type' => 'journal_entry',
                'reference_id' => $entry->id,
                'lines' => [],
            ];

            // Reverse all lines
            foreach ($entry->lines as $line) {
                $reversingData['lines'][] = [
                    'account_id' => $line->account_id,
                    'type' => $line->type === 'debit' ? 'credit' : 'debit',
                    'amount' => $line->amount,
                    'description' => 'Reversal: '.($line->description ?? ''),
                ];
            }

            $reversingEntry = $this->create($reversingData);

            // Mark original as reversed
            $this->journalRepository->update($entry, [
                'is_reversed' => true,
                'reversed_by' => auth()->id(),
                'reversed_at' => now(),
            ]);

            DB::commit();

            return $reversingEntry;
        } catch (Exception $e) {
            DB::rollBack();
            Log::error('Journal entry reversal failed: '.$e->getMessage());
            throw $e;
        }
    }

    /**
     * Post entry to accounts
     */
    private function postEntry(JournalEntry $entry): void
    {
        foreach ($entry->lines as $line) {
            $account = $this->accountRepository->getById($line->account_id);
            if ($account) {
                $this->accountRepository->updateBalance($account, $line->amount, $line->type);
            }
        }
    }

    /**
     * Create journal entry lines
     */
    private function createJournalLines(JournalEntry $entry, array $lines): void
    {
        foreach ($lines as $index => $line) {
            JournalEntryLine::create([
                'journal_entry_id' => $entry->id,
                'account_id' => $line['account_id'],
                'type' => $line['type'],
                'amount' => $line['amount'],
                'description' => $line['description'] ?? null,
                'sort_order' => $index,
            ]);
        }
    }

    /**
     * Calculate totals
     */
    private function calculateTotals(array $lines): array
    {
        $debit = 0;
        $credit = 0;

        foreach ($lines as $line) {
            if ($line['type'] === 'debit') {
                $debit += $line['amount'];
            } else {
                $credit += $line['amount'];
            }
        }

        return [
            'debit' => $debit,
            'credit' => $credit,
        ];
    }

    /**
     * Check if entry is balanced
     */
    private function isBalanced(float $debit, float $credit): bool
    {
        return abs($debit - $credit) < 0.01;
    }
}
