<?php

namespace Modules\Accounting\Services;

use App\Models\ActivityLogModel;
use App\Models\BankAccountModel;
use App\Models\CashAccountModel;
use App\Models\ChartOfAccountModel;
use App\Models\CostCenterModel;
use App\Models\ReceiptVoucherModel;

class ReceiptVoucherService
{
    protected ReceiptVoucherModel $vouchers;
    protected ChartOfAccountModel $accounts;
    protected BankAccountModel $banks;
    protected CashAccountModel $cash;
    protected CostCenterModel $costCenters;
    protected JournalEntryService $journals;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->vouchers    = new ReceiptVoucherModel();
        $this->accounts    = new ChartOfAccountModel();
        $this->banks       = new BankAccountModel();
        $this->cash        = new CashAccountModel();
        $this->costCenters = new CostCenterModel();
        $this->journals    = new JournalEntryService();
        $this->activity    = new ActivityLogModel();
    }

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

        $data = $mapped;
        $data['number']     = $this->vouchers->nextNumber();
        $data['status']     = 'draft';
        $data['created_by'] = $actingUserId;
        $data['updated_by'] = $actingUserId;

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

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

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

    public function update(int $id, array $input, ?int $actingUserId): array
    {
        $voucher = $this->vouchers->find($id);
        if (! $voucher) {
            return ['success' => false, 'errors' => ['id' => 'Receipt voucher not found.']];
        }
        if ($voucher['status'] !== 'draft') {
            return ['success' => false, 'errors' => ['status' => 'Only draft receipt vouchers can be edited.']];
        }

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

        $data = $mapped;
        $data['updated_by'] = $actingUserId;

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

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

        return ['success' => true];
    }

    public function post(int $id, ?int $actingUserId): array
    {
        $voucher = $this->vouchers->find($id);
        if (! $voucher) {
            return ['success' => false, 'error' => 'Receipt voucher not found.'];
        }
        if ($voucher['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft receipt vouchers can be posted.'];
        }

        $check = $this->validate([
            'amount'          => (float) $voucher['amount'],
            'account_id'      => (int) $voucher['account_id'],
            'receive_to_type' => $voucher['receive_to_type'],
            'receive_to_id'   => (int) $voucher['receive_to_id'],
            'cost_center_id'  => $voucher['cost_center_id'] !== null ? (int) $voucher['cost_center_id'] : null,
        ]);
        if (! $check['success']) {
            $err = $check['errors']['amount']
                ?? $check['errors']['account_id']
                ?? $check['errors']['receive_to_id']
                ?? $check['errors']['cost_center_id']
                ?? 'Invalid receipt voucher.';

            return ['success' => false, 'error' => $err];
        }

        $receiveCoa = $this->resolveAccountId($voucher['receive_to_type'], (int) $voucher['receive_to_id']);
        if (! $receiveCoa) {
            return ['success' => false, 'error' => 'Could not resolve chart of accounts for receive-to account.'];
        }

        $creditCoa = (int) $voucher['account_id'];
        if ($creditCoa === $receiveCoa) {
            return ['success' => false, 'error' => 'Credit account and receive-to must map to different ledger accounts.'];
        }

        $amount = round((float) $voucher['amount'], 2);
        $desc   = 'Receipt voucher ' . $voucher['number'] . ($voucher['payer'] ? ' — ' . $voucher['payer'] : '');

        $je = $this->journals->createAndPost([
            'entry_date'  => $voucher['voucher_date'],
            'reference'   => $voucher['reference'] ?: $voucher['number'],
            'description' => $desc,
            'lines'       => [
                [
                    'account_id'  => $receiveCoa,
                    'description' => 'Receipt to ' . $voucher['number'],
                    'debit'       => $amount,
                    'credit'      => 0,
                ],
                [
                    'account_id'  => $creditCoa,
                    'description' => 'Receipt ' . $voucher['number'],
                    'debit'       => 0,
                    'credit'      => $amount,
                ],
            ],
        ], $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->vouchers->update($id, [
            'status'           => 'posted',
            'journal_entry_id' => (int) $je['id'],
            'posted_at'        => date('Y-m-d H:i:s'),
            'updated_by'       => $actingUserId,
        ]);
        $this->activity->log('receipt_vouchers', 'posted', $actingUserId, "Posted receipt voucher #{$id}", (string) $id);

        return ['success' => true];
    }

    public function delete(int $id, ?int $actingUserId): array
    {
        $voucher = $this->vouchers->find($id);
        if (! $voucher) {
            return ['success' => false, 'error' => 'Receipt voucher not found.'];
        }
        if ($voucher['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft receipt vouchers can be deleted.'];
        }

        $this->vouchers->delete($id);
        $this->activity->log('receipt_vouchers', 'deleted', $actingUserId, "Deleted receipt voucher #{$id}", (string) $id);

        return ['success' => true];
    }

    public function resolveAccountId(string $type, int $id): ?int
    {
        if ($type === 'bank') {
            $row = $this->banks->find($id);
        } elseif ($type === 'cash') {
            $row = $this->cash->find($id);
        } else {
            return null;
        }

        if (! $row || empty($row['account_id'])) {
            return null;
        }

        return (int) $row['account_id'];
    }

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

        $accountId = (int) ($data['account_id'] ?? 0);
        $account   = $this->accounts->find($accountId);
        if (! $account) {
            return ['success' => false, 'errors' => ['account_id' => 'Account not found.']];
        }
        if (($account['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['account_id' => 'Account must be active.']];
        }
        if (empty($account['is_postable'])) {
            return ['success' => false, 'errors' => ['account_id' => 'Account must be postable.']];
        }

        $recvType = $data['receive_to_type'] ?? '';
        $recvId   = (int) ($data['receive_to_id'] ?? 0);
        $side     = $this->loadSide($recvType, $recvId);
        if (! $side) {
            return ['success' => false, 'errors' => ['receive_to_id' => 'Receive-to account not found.']];
        }
        if (($side['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['receive_to_id' => 'Receive-to account must be active.']];
        }
        if ((int) ($side['account_id'] ?? 0) <= 0) {
            return ['success' => false, 'errors' => ['receive_to_id' => 'Receive-to account must be linked to the chart of accounts.']];
        }

        $ccId = $data['cost_center_id'] ?? null;
        if ($ccId !== null && (int) $ccId > 0) {
            $cc = $this->costCenters->find((int) $ccId);
            if (! $cc) {
                return ['success' => false, 'errors' => ['cost_center_id' => 'Cost center not found.']];
            }
            if (($cc['status'] ?? '') !== 'active') {
                return ['success' => false, 'errors' => ['cost_center_id' => 'Cost center must be active.']];
            }
        }

        return ['success' => true];
    }

    protected function loadSide(string $type, int $id): ?array
    {
        if ($type === 'bank') {
            return $this->banks->find($id);
        }
        if ($type === 'cash') {
            return $this->cash->find($id);
        }

        return null;
    }

    protected function mapInput(array $input): array
    {
        $cc = $input['cost_center_id'] ?? null;
        $cc = ($cc === '' || $cc === null) ? null : (int) $cc;
        if ($cc !== null && $cc <= 0) {
            $cc = null;
        }

        return [
            'voucher_date'    => $input['voucher_date'] ?? date('Y-m-d'),
            'amount'          => round((float) ($input['amount'] ?? 0), 2),
            'account_id'      => (int) ($input['account_id'] ?? 0),
            'receive_to_type' => $input['receive_to_type'] ?? 'bank',
            'receive_to_id'   => (int) ($input['receive_to_id'] ?? 0),
            'cost_center_id'  => $cc,
            'payer'           => trim((string) ($input['payer'] ?? '')) ?: null,
            'reference'       => trim((string) ($input['reference'] ?? '')) ?: null,
            'notes'           => ! empty($input['notes']) ? trim((string) $input['notes']) : null,
        ];
    }
}
