<?php
declare(strict_types=1);

namespace App\Models;

class BankAccount extends BaseModel
{
    protected string $table = 'bank_accounts';

    public function listForUser(int $userId): array
    {
        $statement = $this->db()->prepare('SELECT * FROM user_bank_accounts WHERE user_id = :user_id ORDER BY is_default DESC, id DESC');
        $statement->execute(['user_id' => $userId]);

        return array_map(fn (array $row): array => $this->safeUserAccount($row), $statement->fetchAll());
    }

    public function defaultForUser(int $userId): ?array
    {
        $statement = $this->db()->prepare('SELECT * FROM user_bank_accounts WHERE user_id = :user_id ORDER BY is_default DESC, id DESC LIMIT 1');
        $statement->execute(['user_id' => $userId]);
        $row = $statement->fetch();

        return $row ? $this->safeUserAccount($row) : null;
    }

    public function findForUser(int $id, int $userId): ?array
    {
        $statement = $this->db()->prepare('SELECT * FROM user_bank_accounts WHERE id = :id AND user_id = :user_id LIMIT 1');
        $statement->execute(['id' => $id, 'user_id' => $userId]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function createForUser(int $userId, array $data): array
    {
        $payload = $this->normalizeUserPayload($data);
        $isDefault = !empty($data['is_default']) || $this->listForUser($userId) === [];

        $this->db()->beginTransaction();
        try {
            if ($isDefault) {
                $this->db()->prepare('UPDATE user_bank_accounts SET is_default = 0, updated_at = NOW() WHERE user_id = :user_id')
                    ->execute(['user_id' => $userId]);
            }

            $statement = $this->db()->prepare(
                'INSERT INTO user_bank_accounts
                    (user_id, account_holder, bank_name, iban, identity_or_tax_no, company_name,
                     is_default, is_verified, created_at, updated_at)
                 VALUES
                    (:user_id, :account_holder, :bank_name, :iban, :identity_or_tax_no, :company_name,
                     :is_default, 0, NOW(), NOW())'
            );
            $statement->execute($payload + [
                'user_id' => $userId,
                'is_default' => $isDefault ? 1 : 0,
            ]);
            $id = (int) $this->db()->lastInsertId();
            $this->db()->commit();
        } catch (\Throwable $exception) {
            if ($this->db()->inTransaction()) {
                $this->db()->rollBack();
            }
            throw $exception;
        }

        return $this->safeUserAccount($this->findForUser($id, $userId) ?? []);
    }

    public function updateForUser(int $id, int $userId, array $data): ?array
    {
        $existing = $this->findForUser($id, $userId);
        if (!$existing) {
            return null;
        }

        $payload = $this->normalizeUserPayload($data);
        $isDefault = !empty($data['is_default']);

        $this->db()->beginTransaction();
        try {
            if ($isDefault) {
                $this->db()->prepare('UPDATE user_bank_accounts SET is_default = 0, updated_at = NOW() WHERE user_id = :user_id AND id <> :id')
                    ->execute(['user_id' => $userId, 'id' => $id]);
            }

            $statement = $this->db()->prepare(
                'UPDATE user_bank_accounts
                 SET account_holder = :account_holder,
                     bank_name = :bank_name,
                     iban = :iban,
                     identity_or_tax_no = :identity_or_tax_no,
                     company_name = :company_name,
                     is_default = :is_default,
                     updated_at = NOW()
                 WHERE id = :id AND user_id = :user_id'
            );
            $statement->execute($payload + [
                'id' => $id,
                'user_id' => $userId,
                'is_default' => $isDefault ? 1 : (int) ($existing['is_default'] ?? 0),
            ]);
            $this->db()->commit();
        } catch (\Throwable $exception) {
            if ($this->db()->inTransaction()) {
                $this->db()->rollBack();
            }
            throw $exception;
        }

        return $this->safeUserAccount($this->findForUser($id, $userId) ?? []);
    }

    public function deleteForUser(int $id, int $userId): bool
    {
        $statement = $this->db()->prepare('DELETE FROM user_bank_accounts WHERE id = :id AND user_id = :user_id');
        $statement->execute(['id' => $id, 'user_id' => $userId]);

        return $statement->rowCount() > 0;
    }

    public function listSiteAccounts(bool $activeOnly = false): array
    {
        $sql = 'SELECT * FROM site_bank_accounts';
        if ($activeOnly) {
            $sql .= ' WHERE is_active = 1';
        }
        $sql .= ' ORDER BY is_default DESC, is_active DESC, id DESC';

        return $this->db()->query($sql)->fetchAll();
    }

    public function defaultSiteAccount(): ?array
    {
        $statement = $this->db()->query('SELECT * FROM site_bank_accounts WHERE is_active = 1 ORDER BY is_default DESC, id DESC LIMIT 1');
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function createSiteAccount(array $data): array
    {
        $payload = $this->normalizeSitePayload($data);
        $isDefault = !empty($data['is_default']) || $this->listSiteAccounts(true) === [];

        $this->db()->beginTransaction();
        try {
            if ($isDefault) {
                $this->db()->exec('UPDATE site_bank_accounts SET is_default = 0, updated_at = NOW()');
            }

            $statement = $this->db()->prepare(
                'INSERT INTO site_bank_accounts
                    (bank_name, account_holder, iban, branch_code, account_no, currency, is_default, is_active,
                     description, created_at, updated_at)
                 VALUES
                    (:bank_name, :account_holder, :iban, :branch_code, :account_no, :currency, :is_default, :is_active,
                     :description, NOW(), NOW())'
            );
            $statement->execute($payload + [
                'is_default' => $isDefault ? 1 : 0,
                'is_active' => array_key_exists('is_active', $data) ? (!empty($data['is_active']) ? 1 : 0) : 1,
            ]);
            $id = (int) $this->db()->lastInsertId();
            $this->db()->commit();
        } catch (\Throwable $exception) {
            if ($this->db()->inTransaction()) {
                $this->db()->rollBack();
            }
            throw $exception;
        }

        return $this->siteAccount($id) ?? [];
    }

    public function updateSiteAccount(int $id, array $data): ?array
    {
        $existing = $this->siteAccount($id);
        if (!$existing) {
            return null;
        }

        $payload = $this->normalizeSitePayload($data);
        $isDefault = !empty($data['is_default']);

        $this->db()->beginTransaction();
        try {
            if ($isDefault) {
                $this->db()->prepare('UPDATE site_bank_accounts SET is_default = 0, updated_at = NOW() WHERE id <> :id')
                    ->execute(['id' => $id]);
            }

            $statement = $this->db()->prepare(
                'UPDATE site_bank_accounts
                 SET bank_name = :bank_name,
                     account_holder = :account_holder,
                     iban = :iban,
                     branch_code = :branch_code,
                     account_no = :account_no,
                     currency = :currency,
                     is_default = :is_default,
                     is_active = :is_active,
                     description = :description,
                     updated_at = NOW()
                 WHERE id = :id'
            );
            $statement->execute($payload + [
                'id' => $id,
                'is_default' => $isDefault ? 1 : (int) ($existing['is_default'] ?? 0),
                'is_active' => !empty($data['is_active']) ? 1 : 0,
            ]);
            $this->db()->commit();
        } catch (\Throwable $exception) {
            if ($this->db()->inTransaction()) {
                $this->db()->rollBack();
            }
            throw $exception;
        }

        return $this->siteAccount($id);
    }

    public function deactivateSiteAccount(int $id): bool
    {
        $statement = $this->db()->prepare('UPDATE site_bank_accounts SET is_active = 0, is_default = 0, updated_at = NOW() WHERE id = :id');
        $statement->execute(['id' => $id]);

        return $statement->rowCount() > 0;
    }

    public function siteAccount(int $id): ?array
    {
        $statement = $this->db()->prepare('SELECT * FROM site_bank_accounts WHERE id = :id LIMIT 1');
        $statement->execute(['id' => $id]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function validateIban(string $iban): bool
    {
        $iban = strtoupper(preg_replace('/\s+/', '', $iban) ?: '');

        return (bool) preg_match('/^TR[0-9]{24}$/', $iban);
    }

    public function maskIban(string $iban): string
    {
        $iban = strtoupper(preg_replace('/\s+/', '', $iban) ?: '');
        if (strlen($iban) < 10) {
            return $iban;
        }

        return substr($iban, 0, 6) . str_repeat('*', max(0, strlen($iban) - 10)) . substr($iban, -4);
    }

    public function maskIdentity(?string $value): string
    {
        $value = preg_replace('/\D+/', '', (string) $value) ?: '';
        if (strlen($value) <= 4) {
            return $value === '' ? '' : str_repeat('*', strlen($value));
        }

        return substr($value, 0, 2) . str_repeat('*', max(0, strlen($value) - 4)) . substr($value, -2);
    }

    private function normalizeUserPayload(array $data): array
    {
        $iban = strtoupper(preg_replace('/\s+/', '', (string) ($data['iban'] ?? '')) ?: '');
        if (!$this->validateIban($iban)) {
            throw new \InvalidArgumentException('IBAN formati gecersiz.');
        }

        $accountHolder = $this->clean((string) ($data['account_holder'] ?? ''), 160);
        $bankName = $this->clean((string) ($data['bank_name'] ?? ''), 120);
        if ($accountHolder === '' || $bankName === '') {
            throw new \InvalidArgumentException('Hesap sahibi ve banka adi zorunlu.');
        }

        return [
            'account_holder' => $accountHolder,
            'bank_name' => $bankName,
            'iban' => $iban,
            'identity_or_tax_no' => $this->clean((string) ($data['identity_or_tax_no'] ?? ''), 40) ?: null,
            'company_name' => $this->clean((string) ($data['company_name'] ?? ''), 160) ?: null,
        ];
    }

    private function normalizeSitePayload(array $data): array
    {
        $iban = strtoupper(preg_replace('/\s+/', '', (string) ($data['iban'] ?? '')) ?: '');
        if (!$this->validateIban($iban)) {
            throw new \InvalidArgumentException('IBAN formati gecersiz.');
        }

        $bankName = $this->clean((string) ($data['bank_name'] ?? ''), 120);
        $accountHolder = $this->clean((string) ($data['account_holder'] ?? ''), 160);
        if ($bankName === '' || $accountHolder === '') {
            throw new \InvalidArgumentException('Banka adi ve hesap sahibi zorunlu.');
        }

        return [
            'bank_name' => $bankName,
            'account_holder' => $accountHolder,
            'iban' => $iban,
            'branch_code' => $this->clean((string) ($data['branch_code'] ?? ''), 40) ?: null,
            'account_no' => $this->clean((string) ($data['account_no'] ?? ''), 80) ?: null,
            'currency' => strtoupper(substr((string) ($data['currency'] ?? 'TRY'), 0, 3)) ?: 'TRY',
            'description' => $this->clean((string) ($data['description'] ?? ''), 255) ?: null,
        ];
    }

    private function safeUserAccount(array $row): array
    {
        if ($row === []) {
            return [];
        }

        $row['iban_masked'] = $this->maskIban((string) ($row['iban'] ?? ''));
        $row['identity_or_tax_no_masked'] = $this->maskIdentity($row['identity_or_tax_no'] ?? null);

        return $row;
    }

    private function clean(string $value, int $limit): string
    {
        $value = trim(preg_replace('/\s+/', ' ', $value) ?: '');

        return function_exists('mb_substr') ? mb_substr($value, 0, $limit) : substr($value, 0, $limit);
    }
}
