# frozen_string_literal: true

# Generated by forge 1.0.0 for NovapayService. Fixtures: fixtures.json (same directory).
require_relative 'generated_spec_helper'
require_relative 'novapay_service'

RSpec.describe Provider::NovapayService do
  subject(:service) { described_class.new(provider: build_record('novapay'), operations: operations) }

  let(:operations) { Provider::MemoryOperations.new }
  let(:operation) { build_operation.tap { |op| operations.save(op) } }
  let(:create_fixture) { fixtures['create_request'] }
  let(:provider_id) { dig_path(create_fixture['response_201'], ["id"]) }
  let(:create_url) { "#{described_class::BASE_URL}/payouts" }
  let(:auth_headers) { { 'X-API-Key' => 'test_api_key' } }

  describe '#check_conditions' do
    it 'rejects amount below minimum' do
      operation.amount = described_class::MIN_AMOUNT - 0.01
      expect(service.check_conditions(operation, 'create').code).to eq('amount_too_low')
    end

    it 'accepts the fixture operation' do
      expect(service.check_conditions(operation, 'create')).to be_success
    end
  end

  describe '#create_request' do
    it 'creates payout (201)' do
      stub = stub_request(:post, create_url).with(headers: auth_headers) { |req| subset_of?(parse_body(req), create_fixture['request']) }
                                            .to_return(status: 201, body: create_fixture['response_201'].to_json,
                                                       headers: { 'Content-Type' => 'application/json' })
      result = service.create_request(operation, 'create')
      expect(result).to be_success
      expect(stub).to have_been_requested
      expect(operations.find(operation.id).provider_operation_key).to eq(provider_id)
      expect(result.data.dig(:result, :id)).to eq(provider_id) # платформа: provider_operation_key = payload.dig(:result, :id)
      expect(result.data[:status]).to eq(create_fixture['expected_operation_status'])
    end

    it 'maps 422 to provider.validation_error' do
      stub_request(:post, create_url).to_return(status: 422, body: (create_fixture['response_422'] || {}).to_json,
                                                headers: { 'Content-Type' => 'application/json' })
      expect(service.create_request(operation, 'create').code).to eq('provider.validation_error')
    end

    it 'refuses an operation without a known requisite type' do
      operation.payout_requisite = { 'unknown_type' => {} }
      expect(service.create_request(operation, 'create').code).to eq('requisite_missing')
    end

    it 'accepts the flat card form of payout_requisite' do
      operation.payout_requisite = { 'card_number' => "4111111111111111", 'holder' => "CARD HOLDER", 'expiry_month' => 12, 'expiry_year' => 2030, 'phone' => "79000000000" }
      stub = stub_request(:post, create_url).to_return(status: 201, body: create_fixture['response_201'].to_json,
                                                     headers: { 'Content-Type' => 'application/json' })
      expect(service.create_request(operation, 'create')).to be_success
      expect(stub).to have_been_requested
    end

    it 'maps 401 to invalid_credentials' do
      stub_request(:post, create_url).to_return(status: 401, body: '{}')
      expect(service.create_request(operation, 'create').code).to eq('provider.invalid_credentials')
    end

    it 'maps 429 to rate_limit with retry_after' do
      stub_request(:post, create_url).to_return(status: 429, body: '{}', headers: { 'Retry-After' => '60' })
      result = service.create_request(operation, 'create')
      expect(result.code).to eq('provider.rate_limit')
      expect(result.data[:retry_after]).to eq(60)
    end

    it 'maps 5xx to provider.unavailable' do
      stub_request(:post, create_url).to_return(status: 503, body: '{}')
      expect(service.create_request(operation, 'create').code).to eq('provider.unavailable')
    end

    it 'delegates status request_method to fetch_status' do
      operation.provider_operation_key = provider_id
      stub = stub_request(:get, "#{described_class::BASE_URL}/payouts/#{provider_id}")
             .to_return(status: 200, body: fixtures.dig('fetch_status', 'response_200').to_json,
                        headers: { 'Content-Type' => 'application/json' })
      service.create_request(operation, 'status')
      expect(stub).to have_been_requested
    end
  end

  describe '#fetch_status' do
    it 'maps the provider status to approved' do
      operation.provider_operation_key = provider_id
      stub_request(:get, "#{described_class::BASE_URL}/payouts/#{provider_id}")
        .to_return(status: 200, body: fixtures.dig('fetch_status', 'response_200').to_json,
                   headers: { 'Content-Type' => 'application/json' })
      result = service.fetch_status(operation)
      expect(result).to be_success
      expect(result.data[:status]).to eq('approved')
    end
  end

  describe '#process_callback' do
    let(:callback) { fixtures['callback'] }
    let(:body) { JSON.generate(callback['payload']) }
    let(:headers) { { 'X-NovaPay-Signature' => sign(body, algorithm: 'sha256', encoding: 'hex') } }

    before do
      operation.provider_operation_key = dig_path(callback['payload'], ["payout_id"])
      operations.save(operation)
    end

    it 'approves on payout.completed with valid signature' do
      result = service.process_callback(callback['payload'], raw_body: body, headers: headers)
      expect(result).to be_success
      expect(result.data[:status]).to eq('approved')
    end

    it 'rejects on payout.failed' do
      failed = fixtures['callback_failed']['payload']
      failed_body = JSON.generate(failed)
      failed_headers = { 'X-NovaPay-Signature' => sign(failed_body, algorithm: 'sha256', encoding: 'hex') }
      operation.provider_operation_key = dig_path(failed, ["payout_id"])
      result = service.process_callback(failed, raw_body: failed_body, headers: failed_headers)
      expect(result.data[:status]).to eq('rejected')
      expect(result.data[:error_code]).to eq('recipient_not_found')
    end

    it 'fails on invalid signature' do
      result = service.process_callback(callback['payload'], raw_body: body, headers: { 'X-NovaPay-Signature' => 'bad' })
      expect(result.code).to eq('invalid_signature')
    end
  end

  describe 'NovapayExtras#cancel_request' do
    it 'cancels a pending payout' do
      require_relative 'novapay_extras'
      extras = Provider::NovapayExtras.new(provider: build_record('novapay'), operations: operations)
      operation.provider_operation_key = provider_id
      stub_request(:post, "#{described_class::BASE_URL}/payouts/#{provider_id}/cancel")
        .to_return(status: 200, body: fixtures.dig('cancel', 'response_200').to_json,
                   headers: { 'Content-Type' => 'application/json' })
      expect(extras.cancel_request(operation).data[:status]).to eq('in_progress')
    end
  end
end
