<?php

namespace Modules\Accounting\Services;

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

class BankTransferService
{
    protected BankTransferModel $transfers;
    protected BankAccountModel $banks;
    protected CashAccountModel $cash;
    protected JournalEntryService $journals;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->transfers = new BankTransferModel();
        $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->validateAccounts($mapped);
        if (! $check['success']) {
            return $check;
        }

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

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

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

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

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

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

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

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

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

        return ['success' => true];
    }

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

        $check = $this->validateAccounts([
            'from_type' => $transfer['from_type'],
            'from_id'   => (int) $transfer['from_id'],
            'to_type'   => $transfer['to_type'],
            'to_id'     => (int) $transfer['to_id'],
            'amount'    => (float) $transfer['amount'],
        ]);
        if (! $check['success']) {
            return ['success' => false, 'error' => $check['errors']['from_id'] ?? $check['errors']['to_id'] ?? $check['errors']['amount'] ?? 'Invalid transfer accounts.'];
        }

        $fromCoa = $this->resolveAccountId($transfer['from_type'], (int) $transfer['from_id']);
        $toCoa   = $this->resolveAccountId($transfer['to_type'], (int) $transfer['to_id']);
        if (! $fromCoa || ! $toCoa) {
            return ['success' => false, 'error' => 'Could not resolve chart of accounts for transfer sides.'];
        }
        if ($fromCoa === $toCoa) {
            return ['success' => false, 'error' => 'From and to must map to different ledger accounts.'];
        }

        $amount = round((float) $transfer['amount'], 2);
        $desc   = 'Transfer ' . $transfer['number'] . ': Debit TO coa, Credit FROM coa';

        $je = $this->journals->createAndPost([
            'entry_date'  => $transfer['transfer_date'],
            'reference'   => $transfer['reference'] ?: $transfer['number'],
            'description' => $desc,
            'lines'       => [
                [
                    'account_id'  => $toCoa,
                    'description' => 'Transfer in ' . $transfer['number'],
                    'debit'       => $amount,
                    'credit'      => 0,
                ],
                [
                    'account_id'  => $fromCoa,
                    'description' => 'Transfer out ' . $transfer['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->transfers->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('bank_transfers', 'posted', $actingUserId, "Posted bank transfer #{$id}", (string) $id);

        return ['success' => true];
    }

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

        $this->transfers->delete($id);
        $this->activity->log('bank_transfers', 'deleted', $actingUserId, "Deleted bank transfer #{$id}", (string) $id);

        return ['success' => true];
    }

    /**
     * Resolve a bank/cash account row to its CoA account_id.
     */
    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 validateAccounts(array $data): array
    {
        $amount = round((float) ($data['amount'] ?? 0), 2);
        if ($amount <= 0) {
            return ['success' => false, 'errors' => ['amount' => 'Amount must be greater than zero.']];
        }

        $fromType = $data['from_type'] ?? '';
        $toType   = $data['to_type'] ?? '';
        $fromId   = (int) ($data['from_id'] ?? 0);
        $toId     = (int) ($data['to_id'] ?? 0);

        if ($fromType === $toType && $fromId === $toId) {
            return ['success' => false, 'errors' => ['to_id' => 'From and to accounts must be different.']];
        }

        $from = $this->loadSide($fromType, $fromId);
        if (! $from) {
            return ['success' => false, 'errors' => ['from_id' => 'From account not found.']];
        }
        if (($from['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['from_id' => 'From account must be active.']];
        }

        $to = $this->loadSide($toType, $toId);
        if (! $to) {
            return ['success' => false, 'errors' => ['to_id' => 'To account not found.']];
        }
        if (($to['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['to_id' => 'To account must be active.']];
        }

        $fromCoa = (int) ($from['account_id'] ?? 0);
        $toCoa   = (int) ($to['account_id'] ?? 0);
        if ($fromCoa <= 0 || $toCoa <= 0) {
            return ['success' => false, 'errors' => ['from_id' => 'Accounts must be linked to the chart of accounts.']];
        }
        if ($fromCoa === $toCoa) {
            return ['success' => false, 'errors' => ['to_id' => 'From and to must map to different ledger accounts.']];
        }

        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
    {
        return [
            'from_type'     => $input['from_type'] ?? 'bank',
            'from_id'       => (int) ($input['from_id'] ?? 0),
            'to_type'       => $input['to_type'] ?? 'bank',
            'to_id'         => (int) ($input['to_id'] ?? 0),
            'transfer_date' => $input['transfer_date'] ?? date('Y-m-d'),
            'amount'        => round((float) ($input['amount'] ?? 0), 2),
            'reference'     => trim((string) ($input['reference'] ?? '')) ?: null,
            'notes'         => ! empty($input['notes']) ? trim((string) $input['notes']) : null,
        ];
    }
}
