<?php

namespace Modules\Accounting\Services;

use App\Models\ActivityLogModel;
use App\Models\BankAccountModel;
use App\Models\CashAccountModel;
use App\Models\ChequeModel;

class ChequeService
{
    protected ChequeModel $cheques;
    protected BankAccountModel $banks;
    protected CashAccountModel $cash;
    protected JournalEntryService $journals;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->cheques  = new ChequeModel();
        $this->banks    = new BankAccountModel();
        $this->cash     = new CashAccountModel();
        $this->journals = new JournalEntryService();
        $this->activity = new ActivityLogModel();
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $mapped = $this->mapInput($input);
        $check  = $this->validateSides($mapped);
        if (! $check['success']) {
            return $check;
        }

        $direction = $mapped['direction'];
        $data = $mapped;
        $data['number']     = $this->cheques->nextNumber();
        $data['status']     = $direction === 'issued' ? 'issued' : 'registered';
        $data['created_by'] = $actingUserId;
        $data['updated_by'] = $actingUserId;

        if (! $this->cheques->insert($data, true)) {
            return ['success' => false, 'errors' => $this->cheques->errors()];
        }

        $id = (int) $this->cheques->getInsertID();
        $this->activity->log('cheques', 'created', $actingUserId, "Created cheque #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function update(int $id, array $input, ?int $actingUserId): array
    {
        $cheque = $this->cheques->find($id);
        if (! $cheque) {
            return ['success' => false, 'errors' => ['id' => 'Cheque not found.']];
        }
        if (! in_array($cheque['status'], ['registered', 'issued'], true)) {
            return ['success' => false, 'errors' => ['status' => 'Only registered or issued cheques can be edited.']];
        }

        $mapped = $this->mapInput($input);
        $check  = $this->validateSides($mapped);
        if (! $check['success']) {
            return $check;
        }

        // Keep workflow status for issued/registered based on direction if still editable
        $data = $mapped;
        if ($mapped['direction'] === 'issued' && $cheque['status'] === 'registered') {
            $data['status'] = 'issued';
        } elseif ($mapped['direction'] === 'received' && $cheque['status'] === 'issued') {
            $data['status'] = 'registered';
        }
        $data['updated_by'] = $actingUserId;

        if (! $this->cheques->update($id, $data)) {
            return ['success' => false, 'errors' => $this->cheques->errors()];
        }

        $this->activity->log('cheques', 'updated', $actingUserId, "Updated cheque #{$id}", (string) $id);

        return ['success' => true];
    }

    public function clear(int $id, ?int $actingUserId): array
    {
        $cheque = $this->cheques->find($id);
        if (! $cheque) {
            return ['success' => false, 'error' => 'Cheque not found.'];
        }
        if (! in_array($cheque['status'], ['registered', 'issued'], true)) {
            return ['success' => false, 'error' => 'Only registered or issued cheques can be cleared.'];
        }

        $bankId = (int) ($cheque['bank_account_id'] ?? 0);
        $cashId = (int) ($cheque['cash_account_id'] ?? 0);
        if ($bankId <= 0) {
            return ['success' => false, 'error' => 'Bank account is required to clear this cheque.'];
        }
        if ($cashId <= 0) {
            return ['success' => false, 'error' => 'Cash/offset account required to clear this cheque.'];
        }

        $bank = $this->banks->find($bankId);
        $cash = $this->cash->find($cashId);
        if (! $bank || empty($bank['account_id'])) {
            return ['success' => false, 'error' => 'Bank account not found or not linked to CoA.'];
        }
        if (! $cash || empty($cash['account_id'])) {
            return ['success' => false, 'error' => 'Cash account not found or not linked to CoA.'];
        }

        $bankCoa = (int) $bank['account_id'];
        $cashCoa = (int) $cash['account_id'];
        if ($bankCoa === $cashCoa) {
            return ['success' => false, 'error' => 'Set a cash account (or different offset) to clear this cheque.'];
        }

        $amount    = round((float) $cheque['amount'], 2);
        $direction = $cheque['direction'];
        $desc      = 'Cheque clear ' . $cheque['number'] . ' (' . $cheque['cheque_number'] . ')';

        if ($direction === 'issued') {
            // Outflow: Credit bank, Debit cash/offset
            $lines = [
                [
                    'account_id'  => $cashCoa,
                    'description' => $desc,
                    'debit'       => $amount,
                    'credit'      => 0,
                ],
                [
                    'account_id'  => $bankCoa,
                    'description' => $desc,
                    'debit'       => 0,
                    'credit'      => $amount,
                ],
            ];
        } else {
            // Received: Debit bank, Credit cash/offset
            $lines = [
                [
                    'account_id'  => $bankCoa,
                    'description' => $desc,
                    'debit'       => $amount,
                    'credit'      => 0,
                ],
                [
                    'account_id'  => $cashCoa,
                    'description' => $desc,
                    'debit'       => 0,
                    'credit'      => $amount,
                ],
            ];
        }

        $je = $this->journals->createAndPost([
            'entry_date'  => date('Y-m-d'),
            'reference'   => $cheque['cheque_number'],
            'description' => $desc,
            'lines'       => $lines,
        ], $actingUserId);

        if (! $je['success']) {
            $err = $je['error'] ?? null;
            if (! $err && ! empty($je['errors'])) {
                $err = $je['errors']['lines'] ?? (is_array($je['errors']) ? reset($je['errors']) : null);
            }

            return ['success' => false, 'error' => $err ?: 'Failed to post journal entry.'];
        }

        $this->cheques->update($id, [
            'status'           => 'cleared',
            'cleared_at'       => date('Y-m-d H:i:s'),
            'journal_entry_id' => (int) $je['id'],
            'updated_by'       => $actingUserId,
        ]);
        $this->activity->log('cheques', 'cleared', $actingUserId, "Cleared cheque #{$id}", (string) $id);

        return ['success' => true];
    }

    public function bounce(int $id, ?int $actingUserId): array
    {
        $cheque = $this->cheques->find($id);
        if (! $cheque) {
            return ['success' => false, 'error' => 'Cheque not found.'];
        }
        if ($cheque['status'] === 'cleared') {
            return ['success' => false, 'error' => 'Cleared cheques cannot be bounced.'];
        }
        if (in_array($cheque['status'], ['bounced', 'cancelled'], true)) {
            return ['success' => false, 'error' => 'Cheque cannot be bounced.'];
        }

        $this->cheques->update($id, ['status' => 'bounced', 'updated_by' => $actingUserId]);
        $this->activity->log('cheques', 'bounced', $actingUserId, "Bounced cheque #{$id}", (string) $id);

        return ['success' => true];
    }

    public function cancel(int $id, ?int $actingUserId): array
    {
        $cheque = $this->cheques->find($id);
        if (! $cheque) {
            return ['success' => false, 'error' => 'Cheque not found.'];
        }
        if ($cheque['status'] === 'cleared') {
            return ['success' => false, 'error' => 'Cleared cheques cannot be cancelled.'];
        }
        if ($cheque['status'] === 'cancelled') {
            return ['success' => false, 'error' => 'Cheque is already cancelled.'];
        }

        $this->cheques->update($id, ['status' => 'cancelled', 'updated_by' => $actingUserId]);
        $this->activity->log('cheques', 'cancelled', $actingUserId, "Cancelled cheque #{$id}", (string) $id);

        return ['success' => true];
    }

    public function delete(int $id, ?int $actingUserId): array
    {
        $cheque = $this->cheques->find($id);
        if (! $cheque) {
            return ['success' => false, 'error' => 'Cheque not found.'];
        }
        if (! in_array($cheque['status'], ['registered', 'issued'], true)) {
            return ['success' => false, 'error' => 'Only registered or issued cheques can be deleted.'];
        }

        $this->cheques->delete($id);
        $this->activity->log('cheques', 'deleted', $actingUserId, "Deleted cheque #{$id}", (string) $id);

        return ['success' => true];
    }

    protected function validateSides(array $data): array
    {
        if (round((float) ($data['amount'] ?? 0), 2) <= 0) {
            return ['success' => false, 'errors' => ['amount' => 'Amount must be greater than zero.']];
        }

        $bankId = (int) ($data['bank_account_id'] ?? 0);
        $cashId = (int) ($data['cash_account_id'] ?? 0);

        if ($bankId > 0) {
            $bank = $this->banks->find($bankId);
            if (! $bank) {
                return ['success' => false, 'errors' => ['bank_account_id' => 'Bank account not found.']];
            }
            if ($bank['status'] !== 'active') {
                return ['success' => false, 'errors' => ['bank_account_id' => 'Bank account must be active.']];
            }
        }

        if ($cashId > 0) {
            $cash = $this->cash->find($cashId);
            if (! $cash) {
                return ['success' => false, 'errors' => ['cash_account_id' => 'Cash account not found.']];
            }
            if ($cash['status'] !== 'active') {
                return ['success' => false, 'errors' => ['cash_account_id' => 'Cash account must be active.']];
            }
        }

        if ($data['direction'] === 'issued' && $bankId <= 0) {
            return ['success' => false, 'errors' => ['bank_account_id' => 'Bank account is required for issued cheques.']];
        }

        return ['success' => true];
    }

    protected function mapInput(array $input): array
    {
        return [
            'cheque_number'   => trim((string) ($input['cheque_number'] ?? '')),
            'direction'       => ($input['direction'] ?? 'issued') === 'received' ? 'received' : 'issued',
            'party_name'      => trim((string) ($input['party_name'] ?? '')),
            'bank_account_id' => ! empty($input['bank_account_id']) ? (int) $input['bank_account_id'] : null,
            'cash_account_id' => ! empty($input['cash_account_id']) ? (int) $input['cash_account_id'] : null,
            'cheque_date'     => $input['cheque_date'] ?? date('Y-m-d'),
            'amount'          => round((float) ($input['amount'] ?? 0), 2),
            'notes'           => ! empty($input['notes']) ? trim((string) $input['notes']) : null,
        ];
    }
}
