<?php

namespace App\PaymentMethod\Service;

use App\Auth\Entity\User;
use App\PaymentMethod\Entity\BankAccount;
use App\PaymentMethod\Entity\Pos;
use App\PaymentMethod\Repository\BankAccountRepository;
use App\PaymentMethod\Repository\PosRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Context\EntityContext;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;

/**
 * Business logic for an environment's payment methods (bank accounts + POS
 * devices). Ported from clinic-pro-tauri PaymentManagement tab.
 *
 * اسکوپ **محیط** است نه کاربر: پزشکی که هم مطب شخصی دارد و هم کلینیک، در هر محیط
 * کارت‌های همان محیط را می‌بیند. `user_id` هنگام ساخت از کاربر جاری پر می‌شود، ولی
 * فقط می‌گوید چه کسی ثبتش کرده.
 *
 * ردیف‌های بازمانده از پیش از فاز ۶ محیط تهی دارند و در هیچ محیطی دیده نمی‌شوند؛
 * مالکشان آن‌ها را در فهرست «بی‌محیط» می‌بیند و با assign*Environment به محیط
 * فعالش می‌چسباند.
 */
class PaymentMethodService
{
    public function __construct(
        private readonly BankAccountRepository  $bankRepo,
        private readonly PosRepository          $posRepo,
        private readonly TenantOwnershipChecker $tenantOwnership,
    ) {}

    // ---- Bank accounts -----------------------------------------------------

    /** @return array<int, array<string, mixed>> */
    public function listBankAccounts(EntityContext $context, User $user): array
    {
        [$type, $id] = $this->pair($context);

        return array_merge(
            array_map(static fn (BankAccount $b) => $b->toArray(), $this->bankRepo->findByEntity($type, $id)),
            $this->bankRepo->findUnassignedByUser($user),
        );
    }

    public function createBankAccount(EntityContext $context, User $user, array $data): array
    {
        $bankName      = trim((string) ($data['bank_name'] ?? ''));
        $accountNumber = trim((string) ($data['account_number'] ?? ''));
        $cardNumber    = trim((string) ($data['card_number'] ?? ''));
        $shabaNumber   = trim((string) ($data['shaba_number'] ?? ''));

        if ($bankName === '') {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'نام بانک الزامی است', 422, 'bank_name');
        }
        if ($accountNumber === '') {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'شماره حساب الزامی است', 422, 'account_number');
        }

        $this->pair($context);

        $account = new BankAccount($user, $bankName, $accountNumber, $cardNumber, $shabaNumber);
        $account->assignTenant($context);
        $this->bankRepo->save($account);

        return $account->toArray();
    }

    public function updateBankAccount(EntityContext $context, string $uuid, array $data): array
    {
        $account = $this->ownedBankAccount($context, $uuid);

        if (array_key_exists('bank_name', $data)) {
            $bankName = trim((string) $data['bank_name']);
            if ($bankName === '') {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'نام بانک الزامی است', 422, 'bank_name');
            }
            $account->setBankName($bankName);
        }
        if (array_key_exists('account_number', $data)) {
            $accountNumber = trim((string) $data['account_number']);
            if ($accountNumber === '') {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'شماره حساب الزامی است', 422, 'account_number');
            }
            $account->setAccountNumber($accountNumber);
        }
        if (array_key_exists('card_number', $data)) {
            $account->setCardNumber(trim((string) $data['card_number']));
        }
        if (array_key_exists('shaba_number', $data)) {
            $account->setShabaNumber(trim((string) $data['shaba_number']));
        }

        $this->bankRepo->save($account);

        return $account->toArray();
    }

    public function toggleBankAccountStatus(EntityContext $context, string $uuid): array
    {
        $account = $this->ownedBankAccount($context, $uuid);
        $account->setActive(!$account->isActive());
        $this->bankRepo->save($account);

        return $account->toArray();
    }

    /** حسابِ بی‌محیطِ خودِ کاربر را به محیط فعال می‌چسباند. */
    public function assignBankAccountEnvironment(EntityContext $context, User $user, string $uuid): array
    {
        [$type, $id] = $this->pair($context);

        if (!$this->bankRepo->assignEntity($uuid, $user, $type, $id)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'حساب بانکیِ بدون محیط یافت نشد', 404);
        }

        $account = $this->bankRepo->reloadByUuid($uuid);
        if (!$this->tenantOwnership->belongsTo($context, $account)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'حساب بانکی یافت نشد', 404);
        }

        return $account->toArray();
    }

    private function ownedBankAccount(EntityContext $context, string $uuid): BankAccount
    {
        $account = $this->bankRepo->findByUuid($uuid);
        if (!$this->tenantOwnership->belongsTo($context, $account)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'حساب بانکی یافت نشد', 404);
        }

        return $account;
    }

    // ---- POS devices -------------------------------------------------------

    /** @return array<int, array<string, mixed>> */
    public function listPos(EntityContext $context, User $user): array
    {
        [$type, $id] = $this->pair($context);

        return array_merge(
            array_map(static fn (Pos $p) => $p->toArray(), $this->posRepo->findByEntity($type, $id)),
            $this->posRepo->findUnassignedByUser($user),
        );
    }

    public function createPos(EntityContext $context, User $user, array $data): array
    {
        $bankName       = trim((string) ($data['bank_name'] ?? ''));
        $terminalNumber = trim((string) ($data['terminal_number'] ?? ''));
        $serialNumber   = trim((string) ($data['serial_number'] ?? ''));
        $accountNumber  = trim((string) ($data['account_number'] ?? ''));

        if ($bankName === '') {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'نام بانک الزامی است', 422, 'bank_name');
        }
        if ($terminalNumber === '') {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'شماره ترمینال الزامی است', 422, 'terminal_number');
        }

        $this->pair($context);

        $pos = new Pos($user, $bankName, $terminalNumber, $serialNumber, $accountNumber);
        $pos->assignTenant($context);
        $this->posRepo->save($pos);

        return $pos->toArray();
    }

    public function updatePos(EntityContext $context, string $uuid, array $data): array
    {
        $pos = $this->ownedPos($context, $uuid);

        if (array_key_exists('bank_name', $data)) {
            $bankName = trim((string) $data['bank_name']);
            if ($bankName === '') {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'نام بانک الزامی است', 422, 'bank_name');
            }
            $pos->setBankName($bankName);
        }
        if (array_key_exists('terminal_number', $data)) {
            $terminalNumber = trim((string) $data['terminal_number']);
            if ($terminalNumber === '') {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'شماره ترمینال الزامی است', 422, 'terminal_number');
            }
            $pos->setTerminalNumber($terminalNumber);
        }
        if (array_key_exists('serial_number', $data)) {
            $pos->setSerialNumber(trim((string) $data['serial_number']));
        }
        if (array_key_exists('account_number', $data)) {
            $pos->setAccountNumber(trim((string) $data['account_number']));
        }

        $this->posRepo->save($pos);

        return $pos->toArray();
    }

    public function togglePosStatus(EntityContext $context, string $uuid): array
    {
        $pos = $this->ownedPos($context, $uuid);
        $pos->setActive(!$pos->isActive());
        $this->posRepo->save($pos);

        return $pos->toArray();
    }

    /** کارتخوانِ بی‌محیطِ خودِ کاربر را به محیط فعال می‌چسباند. */
    public function assignPosEnvironment(EntityContext $context, User $user, string $uuid): array
    {
        [$type, $id] = $this->pair($context);

        if (!$this->posRepo->assignEntity($uuid, $user, $type, $id)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'کارت خوانِ بدون محیط یافت نشد', 404);
        }

        $pos = $this->posRepo->reloadByUuid($uuid);
        if (!$this->tenantOwnership->belongsTo($context, $pos)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'کارت خوان یافت نشد', 404);
        }

        return $pos->toArray();
    }

    private function ownedPos(EntityContext $context, string $uuid): Pos
    {
        $pos = $this->posRepo->findByUuid($uuid);
        if (!$this->tenantOwnership->belongsTo($context, $pos)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'کارت خوان یافت نشد', 404);
        }

        return $pos;
    }

    /**
     * محیطِ حل‌شده الزامی است: منشیِ بدون محیط فعال یا کاربری که هنوز پزشک/کلینیکی
     * ندارد، نه کارتی برای دیدن دارد نه جایی برای ساختنش.
     *
     * @return array{0: string, 1: int}
     */
    private function pair(EntityContext $context): array
    {
        if (!$context->isResolved()) {
            throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, 'محیط فعالی برای روش‌های پرداخت انتخاب نشده است', 403);
        }

        [$type, $id] = $context->toEntityPair();

        return [$type, (int) $id];
    }
}
