<?php

namespace App\Services\Pagarme;

use App\Data\Pagarme\Recipient\BankAccountUpdateRequestData;
use App\Data\Pagarme\Recipient\Parts\Request\AddressData;
use App\Data\Pagarme\Recipient\Parts\Request\AutomaticAnticipationSettingsData;
use App\Data\Pagarme\Recipient\Parts\Request\BankAccountData;
use App\Data\Pagarme\Recipient\Parts\Request\PhoneData;
use App\Data\Pagarme\Recipient\Parts\Request\PhoneNumbersData;
use App\Data\Pagarme\Recipient\Parts\Request\RegisterInformationData;
use App\Data\Pagarme\Recipient\Parts\Request\TransferSettingsData;
use App\Data\Pagarme\Recipient\RecipientRequestData;
use App\Data\Pagarme\Recipient\RecipientResponseData;
use App\Models\Provider;
use App\Models\ProviderBankAccount;
use App\Services\Pagarme\Concerns\FormatsPagarmeData;
use App\Services\Pagarme\Concerns\MocksPagarmeRequests;
use App\Services\Pagarme\Concerns\SendsPagarmeRequests;

class PagarmeRecipientService
{
    use FormatsPagarmeData;
    use MocksPagarmeRequests;
    use SendsPagarmeRequests;

    public function createRecipientForProvider(Provider $provider, array $data): string
    {
        if (! empty($provider->recipient_id)) {
            return $provider->recipient_id;
        }

        $metadata      = data_get($data, 'recipient_metadata', []);
        $paymentMode   = data_get($data, 'recipient_payment_mode');
        $recipientCode = $provider->ensureGatewayCode();

        $addressParts = $this->extractAddressParts($data);

        $registerInformation = new RegisterInformationData(
            name:                   data_get($data, 'recipient_name'),
            email:                  data_get($data, 'recipient_email'),
            document:               $this->digits(data_get($data, 'recipient_document')),
            type:                   data_get($data, 'recipient_type', 'individual'),
            birthdate:              $this->formatBirthdate(data_get($data, 'birth_date')),
            monthlyIncome:          data_get($data, 'monthly_income') !== null ? (int) data_get($data, 'monthly_income') : 1000,
            professionalOccupation: data_get($data, 'professional_occupation', 'autonomo'),

            phoneNumbers: new PhoneNumbersData(
                $this->buildRecipientPhone(data_get($data, 'phone')),
            ),

            address: new AddressData(
                street:         data_get($addressParts, 'street'),
                complementary:  data_get($addressParts, 'complementary'),
                streetNumber:   data_get($addressParts, 'street_number'),
                neighborhood:   data_get($addressParts, 'neighborhood'),
                city:           data_get($data, 'city'),
                state:          data_get($data, 'state'),
                zipCode:        $this->digits(data_get($data, 'zip_code')),
                referencePoint: data_get($addressParts, 'reference_point'),
            ),
        );

        $defaultBankAccount = $this->buildRecipientBankAccount(
            data_get($data, 'recipient_default_bank_account'),
        );

        $payload = new RecipientRequestData(
            code: $recipientCode,

            registerInformation: $registerInformation,
            defaultBankAccount:  $defaultBankAccount,

            transferSettings: new TransferSettingsData(
                transferEnabled:  false,
                transferInterval: 'Daily',
                transferDay:      0,
            ),

            automaticAnticipationSettings: new AutomaticAnticipationSettingsData(
                enabled: true,
                type:    '1025', // isso significa que vai ser feita de acordo com o delay (dias)
                delay:   1,
            ),
        );

        $bankAccountData = $payload->defaultBankAccount->toArray();

        if ($this->shouldMockPagarmeRequest()) {
            $recipientId = $this->mockPagarmeId('re', $provider->id);

            $provider->forceFill([
                'recipient_id'                   => $recipientId,
                'recipient_name'                 => data_get($data, 'recipient_name'),
                'recipient_email'                => data_get($data, 'recipient_email'),
                'recipient_description'          => data_get($data, 'recipient_description', ''),
                'recipient_document'             => data_get($data, 'recipient_document'),
                'recipient_type'                 => $payload->registerInformation->type,
                'recipient_code'                 => $recipientCode,
                'recipient_payment_mode'         => $paymentMode,
                'recipient_default_bank_account' => $bankAccountData,

                'recipient_transfer_settings' => [
                    'transfer_enabled'  => false,
                    'transfer_interval' => 'daily',
                    'transfer_day'      => 0,
                ],

                'recipient_automatic_anticipation_settings' => [
                    'enabled' => true,
                    'type'    => 'full',
                    'delay'   => 1,
                ],

                'recipient_metadata' => array_merge($metadata, ['mocked' => true]),
            ])->save();

            return $recipientId;
        }

        $raw = $this->pagarmeRequest(
            method:         'POST',
            path:           '/recipients',
            payload:        $payload,
            idempotencyKey: $this->idempotencyKey($provider, '', $data),
            errorMessage:   'Erro ao criar recebedor no Pagar.me.',
        );

        $recipientData = RecipientResponseData::fromArray($raw);

        $recipientId = $recipientData->requireId();

        $provider->forceFill([
            'recipient_id'                   => $recipientId,
            'recipient_name'                 => data_get($data, 'recipient_name'),
            'recipient_email'                => data_get($data, 'recipient_email'),
            'recipient_description'          => data_get($data, 'recipient_description', ''),
            'recipient_document'             => data_get($data, 'recipient_document'),
            'recipient_type'                 => $payload->registerInformation->type,
            'recipient_code'                 => $recipientCode,
            'recipient_payment_mode'         => $paymentMode,
            'recipient_default_bank_account' => $bankAccountData,

            'recipient_transfer_settings' => [
                'transfer_enabled'  => false,
                'transfer_interval' => 'daily',
                'transfer_day'      => 0,
            ],

            'recipient_automatic_anticipation_settings' => [
                'enabled' => true,
                'type'    => 'full',
                'delay'   => 1,
            ],

            'recipient_metadata' => $metadata,
        ])->save();

        return $recipientId;
    }

    public function getRecipient(string $recipientId): array
    {
        if ($this->shouldMockPagarmeRequest()) {
            $provider = Provider::query()->where('recipient_id', $recipientId)->first();

            return [
                'id'                   => $recipientId,
                'default_bank_account' => $provider?->recipient_default_bank_account ?? [],
                'mocked'               => true,
            ];
        }

        return $this->pagarmeRequest(
            method:         'GET',
            path:           "/recipients/{$recipientId}",
            payload:        [],
            idempotencyKey: "get-recipient-{$recipientId}",
            errorMessage:   'Erro ao consultar recebedor no Pagar.me.',
        );
    }

    /**
     * O Pagar.me mantem UMA conta bancaria por recebedor. As demais contas do prestador
     * existem apenas no nosso banco; aqui so trafega a conta principal.
     *
     * O recipient_id nao muda: o que e atualizado e o destino das transferencias (saques).
     */
    public function updateDefaultBankAccount(Provider $provider, ProviderBankAccount $bankAccount): Provider
    {
        $bankAccountData = $bankAccount->toGatewayBankAccount();

        $payload = new BankAccountUpdateRequestData(
            holderName:        $this->normalizeHolderName(data_get($bankAccountData, 'holder_name')),
            holderType:        data_get($bankAccountData, 'holder_type'),
            holderDocument:    $this->digits(data_get($bankAccountData, 'holder_document')),
            bank:              data_get($bankAccountData, 'bank'),
            branchNumber:      data_get($bankAccountData, 'branch_number'),
            branchCheckDigit:  data_get($bankAccountData, 'branch_check_digit'),
            accountNumber:     data_get($bankAccountData, 'account_number'),
            accountCheckDigit: data_get($bankAccountData, 'account_check_digit'),
            type:              data_get($bankAccountData, 'type'),
        );

        if ($this->shouldMockPagarmeRequest()) {
            $provider->forceFill([
                'recipient_default_bank_account' => $payload->toArray()['bank_account'],
            ])->save();

            return $provider->fresh();
        }

        $this->pagarmeRequest(
            method:         'PATCH',
            path:           "/recipients/{$provider->recipient_id}/default-bank-account",
            payload:        $payload,
            idempotencyKey: $this->idempotencyKey($provider, $this->bankAccountIdempotencySuffix($bankAccount)),
            errorMessage:   'Erro ao atualizar conta bancaria do recebedor no Pagar.me.',
        );

        $provider->forceFill([
            'recipient_default_bank_account' => $payload->toArray()['bank_account'],
        ])->save();

        return $provider->fresh();
    }

    /**
     * A chave precisa ser unica por operacao, e nao pelo conteudo da conta.
     *
     * Com o hash do payload, alternar a conta principal A -> B -> A reenviava a mesma chave
     * da primeira troca: o Pagar.me tratava como repeticao, devolvia a resposta antiga e
     * nao aplicava a volta para A - o saque continuaria caindo em B.
     *
     * Por isso o discriminador e um contador persistido, e nao o timestamp da conta:
     * updated_at volta do banco truncado em segundos e repetiria a chave.
     */
    private function bankAccountIdempotencySuffix(ProviderBankAccount $bankAccount): string
    {
        return sprintf(
            'default-bank-account-%d-%d',
            $bankAccount->id,
            $bankAccount->gateway_sync_count,
        );
    }

    //

    private function buildRecipientBankAccount(array $data): BankAccountData
    {
        return new BankAccountData(
            holderName:        $this->normalizeHolderName(data_get($data, 'holder_name')),
            holderType:        data_get($data, 'holder_type'),
            holderDocument:    $this->digits(data_get($data, 'holder_document')),
            bank:              data_get($data, 'bank'),
            branchNumber:      data_get($data, 'branch_number'),
            branchCheckDigit:  data_get($data, 'branch_check_digit'),
            accountNumber:     data_get($data, 'account_number'),
            accountCheckDigit: data_get($data, 'account_check_digit'),
            type:              data_get($data, 'type'),
        );
    }

    private function buildRecipientPhone(?string $phone): PhoneData
    {
        $digits = $this->digits($phone);

        if (strlen($digits) < 10) {
            return new PhoneData(
                ddd:    '11',
                number: '999999999',
                type:   'mobile',
            );
        }

        if (str_starts_with($digits, '55')) {
            $digits = substr($digits, 2);
        }

        return new PhoneData(
            ddd:    substr($digits, 0, 2),
            number: substr($digits, 2),
            type:   'mobile',
        );
    }

    // evita criacao duplica de recipient

    // formato: recipient-{email}-{documento} (tudo minusculo e sem acento)

    private function idempotencyKey(Provider $provider, string $suffix = '', array $data = []): string
    {
        $baseKey = $provider->idempotency_key;

        if (empty($baseKey)) {
            $provider->loadMissing('user');

            $baseKey = $this->pagarmeIdempotencyKey('recipient', [
                data_get($data, 'recipient_email', $provider->recipient_email ?? $provider->user?->email ?? "provider-{$provider->id}"),
                $this->customerDocument(data_get($data, 'recipient_document', $provider->recipient_document ?? $provider->document))
                    ?: "document-provider-{$provider->id}",
            ]);

            $provider->forceFill(['idempotency_key' => $baseKey])->save();
        }

        return $suffix ? "{$baseKey}-{$suffix}" : $baseKey;
    }
}
