<?php

namespace App\Services\Barobill;

use App\Models\Barobill\BarobillConfig;
use Illuminate\Support\Facades\Log;
use SoapClient;
use SoapFault;
use Throwable;

/**
 * 바로빌 API 서비스
 *
 * 바로빌 SOAP 웹서비스를 Laravel에서 사용하기 위한 서비스 클래스
 *
 * @see https://dev.barobill.co.kr/
 * @see https://ws.baroservice.com/CORPSTATE.asmx (회원관리)
 * @see https://ws.baroservice.com/TI.asmx (세금계산서)
 * @see https://ws.baroservice.com/BANKACCOUNT.asmx (계좌조회)
 * @see https://ws.baroservice.com/CARD.asmx (카드조회)
 * @see https://ws.baroservice.com/KAKAOTALK.asmx (카카오톡)
 */
class BarobillService
{
    /**
     * SOAP 클라이언트 (회원관리용 - CORPSTATE)
     */
    protected ?SoapClient $corpStateClient = null;

    /**
     * SOAP 클라이언트 (세금계산서용 - TI)
     */
    protected ?SoapClient $tiClient = null;

    /**
     * SOAP 클라이언트 (계좌조회용 - BANKACCOUNT)
     */
    protected ?SoapClient $bankAccountClient = null;

    /**
     * SOAP 클라이언트 (카드조회용 - CARD)
     */
    protected ?SoapClient $cardClient = null;

    /**
     * SOAP 클라이언트 (카카오톡용 - KAKAOTALK)
     */
    protected ?SoapClient $kakaotalkClient = null;

    /**
     * SOAP 클라이언트 (문자전송용 - SMS)
     */
    protected ?SoapClient $smsClient = null;

    /**
     * CERTKEY (인증키)
     */
    protected string $certKey;

    /**
     * 파트너 사업자번호
     */
    protected string $corpNum;

    /**
     * 테스트 모드 여부
     */
    protected bool $isTestMode;

    /**
     * SOAP URL
     */
    protected array $soapUrls;

    /**
     * 에러 코드 매핑
     */
    protected array $errorMessages = [
        // 공통 오류
        -11101 => '사업자번호가 설정되지 않았거나 유효하지 않습니다.',
        -11102 => 'CERTKEY가 유효하지 않습니다.',
        -11103 => '인증서가 만료되었거나 유효하지 않습니다.',
        -11104 => '해당 사업자가 등록되어 있지 않습니다.',
        -11105 => '이미 등록된 사업자입니다.',
        -11106 => '아이디가 이미 존재합니다.',
        -11201 => '필수 파라미터가 누락되었습니다.',
        // 인증서 관련
        -26001 => '공동인증서가 등록되어 있지 않습니다.',
        // 회원 관련 오류 (RegistCorp)
        -32000 => '알 수 없는 오류가 발생했습니다.',
        -32001 => '사업자번호가 유효하지 않습니다.',
        -32002 => '아이디가 유효하지 않습니다.',
        -32003 => '비밀번호가 유효하지 않습니다.',
        -32004 => '상호명이 유효하지 않습니다.',
        -32005 => '대표자명이 유효하지 않습니다.',
        -32006 => '이메일 형식이 유효하지 않습니다.',
        -32010 => '이미 등록된 사업자번호입니다.',
        -32011 => '이미 등록된 아이디입니다.',
        -32012 => '이미 등록된 아이디입니다. 다른 아이디를 사용해주세요.',
        -32013 => '비밀번호 형식이 유효하지 않습니다. (영문/숫자/특수문자 조합 8자리 이상)',
        -32014 => '연락처 형식이 유효하지 않습니다.',
        -32020 => '파트너 사업자번호가 유효하지 않습니다.',
        -32021 => '파트너 인증키(CERTKEY)가 유효하지 않습니다.',
        // 기타
        -99999 => '서버 내부 오류가 발생했습니다.',
    ];

    public function __construct()
    {
        // 기본값: .env 설정 사용
        $this->isTestMode = config('services.barobill.test_mode', true);
        $this->initializeConfig();
    }

    /**
     * 서버 모드 전환 (회원사별 설정 적용)
     *
     * @param  bool  $isTestMode  true: 테스트서버, false: 운영서버
     */
    public function switchServerMode(bool $isTestMode): self
    {
        if ($this->isTestMode !== $isTestMode) {
            $this->isTestMode = $isTestMode;
            // SOAP 클라이언트 초기화 (새 서버로 재연결)
            $this->corpStateClient = null;
            $this->tiClient = null;
            $this->bankAccountClient = null;
            $this->cardClient = null;
            $this->kakaotalkClient = null;
            // 설정 재로드
            $this->initializeConfig();
        }

        return $this;
    }

    /**
     * 서버 모드 문자열로 전환
     *
     * @param  string  $mode  'test' 또는 'production'
     */
    public function setServerMode(string $mode): self
    {
        return $this->switchServerMode($mode === 'test');
    }

    /**
     * 현재 서버 모드 조회
     */
    public function getServerMode(): string
    {
        return $this->isTestMode ? 'test' : 'production';
    }

    /**
     * 설정 초기화 (서버 모드에 따른 설정 로드)
     */
    protected function initializeConfig(): void
    {
        // DB에서 활성화된 설정 가져오기 (우선순위)
        $dbConfig = $this->loadConfigFromDatabase();

        if ($dbConfig) {
            $this->certKey = $dbConfig->cert_key;
            $this->corpNum = $dbConfig->corp_num ?? '';
            $this->soapUrls = $this->buildSoapUrls($dbConfig->base_url);
        } else {
            // DB 설정이 없으면 .env에서 가져오기 (fallback)
            // 테스트 모드에 따라 적절한 CERT_KEY 선택
            $this->certKey = $this->isTestMode
                ? config('services.barobill.cert_key_test', '')
                : config('services.barobill.cert_key_prod', '');
            $this->corpNum = config('services.barobill.corp_num', '');
            $baseUrl = $this->isTestMode
                ? 'https://testws.baroservice.com'
                : 'https://ws.baroservice.com';
            $this->soapUrls = [
                'corpstate' => $baseUrl.'/CORPSTATE.asmx?WSDL',
                'ti' => $baseUrl.'/TI.asmx?WSDL',
                'bankaccount' => $baseUrl.'/BANKACCOUNT.asmx?WSDL',
                'card' => $baseUrl.'/CARD.asmx?WSDL',
                'kakaotalk' => $baseUrl.'/KAKAOTALK.asmx?WSDL',
                'sms' => $baseUrl.'/SMS.asmx?WSDL',
            ];
        }
    }

    /**
     * DB에서 활성화된 설정 가져오기
     */
    protected function loadConfigFromDatabase(): ?BarobillConfig
    {
        try {
            return BarobillConfig::getActive($this->isTestMode);
        } catch (\Exception $e) {
            Log::warning('바로빌 DB 설정 로드 실패', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * SOAP URL 빌드
     */
    protected function buildSoapUrls(string $baseUrl): array
    {
        // URL에서 후행 슬래시 제거
        $baseUrl = rtrim($baseUrl, '/');

        return [
            'corpstate' => $baseUrl.'/CORPSTATE.asmx?WSDL',
            'ti' => $baseUrl.'/TI.asmx?WSDL',
            'bankaccount' => $baseUrl.'/BANKACCOUNT.asmx?WSDL',
            'card' => $baseUrl.'/CARD.asmx?WSDL',
            'kakaotalk' => $baseUrl.'/KAKAOTALK.asmx?WSDL',
            'sms' => $baseUrl.'/SMS.asmx?WSDL',
        ];
    }

    /**
     * CORPSTATE SOAP 클라이언트 가져오기 (회원관리용)
     */
    protected function getCorpStateClient(): ?SoapClient
    {
        if ($this->corpStateClient === null) {
            try {
                $this->corpStateClient = new SoapClient($this->soapUrls['corpstate'], [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (Throwable $e) {
                Log::error('바로빌 CORPSTATE SOAP 클라이언트 생성 실패', [
                    'error' => $e->getMessage(),
                    'url' => $this->soapUrls['corpstate'],
                ]);

                return null;
            }
        }

        return $this->corpStateClient;
    }

    /**
     * BANKACCOUNT SOAP 클라이언트 가져오기 (계좌조회용)
     */
    protected function getBankAccountClient(): ?SoapClient
    {
        if ($this->bankAccountClient === null) {
            try {
                $this->bankAccountClient = new SoapClient($this->soapUrls['bankaccount'], [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (Throwable $e) {
                Log::error('바로빌 BANKACCOUNT SOAP 클라이언트 생성 실패', [
                    'error' => $e->getMessage(),
                    'url' => $this->soapUrls['bankaccount'],
                ]);

                return null;
            }
        }

        return $this->bankAccountClient;
    }

    /**
     * CARD SOAP 클라이언트 가져오기 (카드조회용)
     */
    protected function getCardClient(): ?SoapClient
    {
        if ($this->cardClient === null) {
            try {
                $this->cardClient = new SoapClient($this->soapUrls['card'], [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (Throwable $e) {
                Log::error('바로빌 CARD SOAP 클라이언트 생성 실패', [
                    'error' => $e->getMessage(),
                    'url' => $this->soapUrls['card'],
                ]);

                return null;
            }
        }

        return $this->cardClient;
    }

    /**
     * TI SOAP 클라이언트 가져오기 (전자세금계산서용)
     */
    protected function getTiClient(): ?SoapClient
    {
        if ($this->tiClient === null) {
            try {
                $this->tiClient = new SoapClient($this->soapUrls['ti'], [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (Throwable $e) {
                Log::error('바로빌 TI SOAP 클라이언트 생성 실패', [
                    'error' => $e->getMessage(),
                    'url' => $this->soapUrls['ti'],
                ]);

                return null;
            }
        }

        return $this->tiClient;
    }

    /**
     * SOAP 메서드 호출
     */
    protected function call(string $service, string $method, array $params = []): array
    {
        $client = match ($service) {
            'corpstate' => $this->getCorpStateClient(),
            'ti' => $this->getTiClient(),
            'bankaccount' => $this->getBankAccountClient(),
            'card' => $this->getCardClient(),
            'kakaotalk' => $this->getKakaotalkClient(),
            'sms' => $this->getSmsClient(),
            default => null,
        };

        if (! $client) {
            return [
                'success' => false,
                'error' => "SOAP 클라이언트가 초기화되지 않았습니다. ({$service})",
                'error_code' => -1,
            ];
        }

        if (empty($this->certKey)) {
            return [
                'success' => false,
                'error' => 'CERTKEY가 설정되지 않았습니다. .env 파일의 BAROBILL_CERT_KEY를 확인하세요.',
                'error_code' => -2,
            ];
        }

        // CERTKEY 자동 추가
        if (! isset($params['CERTKEY'])) {
            $params['CERTKEY'] = $this->certKey;
        }

        try {
            Log::info('바로빌 API 호출', [
                'service' => $service,
                'method' => $method,
                'test_mode' => $this->isTestMode,
            ]);

            $result = $client->$method($params);
            $resultProperty = $method.'Result';

            if (isset($result->$resultProperty)) {
                $resultData = $result->$resultProperty;

                // 음수는 에러 코드
                if (is_numeric($resultData) && $resultData < 0) {
                    $errorMessage = $this->errorMessages[$resultData] ?? "바로빌 API 오류 코드: {$resultData}";

                    Log::error('바로빌 API 오류', [
                        'method' => $method,
                        'error_code' => $resultData,
                        'error_message' => $errorMessage,
                    ]);

                    return [
                        'success' => false,
                        'error' => $errorMessage,
                        'error_code' => $resultData,
                    ];
                }

                // 양수(또는 0)는 성공
                return [
                    'success' => true,
                    'data' => $resultData,
                ];
            }

            return [
                'success' => true,
                'data' => $result,
            ];

        } catch (SoapFault $e) {
            Log::error('바로빌 SOAP 오류', [
                'method' => $method,
                'fault_code' => $e->faultcode ?? null,
                'fault_string' => $e->faultstring ?? null,
            ]);

            return [
                'success' => false,
                'error' => 'SOAP 오류: '.$e->getMessage(),
                'error_code' => $e->getCode(),
            ];

        } catch (Throwable $e) {
            Log::error('바로빌 API 호출 오류', [
                'method' => $method,
                'error' => $e->getMessage(),
            ]);

            return [
                'success' => false,
                'error' => 'API 호출 오류: '.$e->getMessage(),
                'error_code' => -999,
            ];
        }
    }

    /**
     * 회원사 등록 (RegistCorp)
     *
     * @see https://ws.baroservice.com/CORPSTATE.asmx?op=RegistCorp
     */
    public function registCorp(array $data): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($data['biz_no']),
            'CorpName' => $data['corp_name'],
            'CEOName' => $data['ceo_name'],
            'BizType' => $data['biz_type'] ?? '',
            'BizClass' => $data['biz_class'] ?? '',
            'PostNum' => $data['post_num'] ?? '',
            'Addr1' => $data['addr'] ?? '',
            'Addr2' => '',
            'MemberName' => $data['manager_name'] ?? '',
            'JuminNum' => '', // 주민번호 (필요시)
            'ID' => $data['barobill_id'],
            'PWD' => $data['barobill_pwd'],
            'Grade' => '2', // 1: 파트너, 2: 회원사
            'TEL' => $data['tel'] ?? '',
            'HP' => $data['manager_hp'] ?? '',
            'Email' => $data['manager_email'] ?? '',
        ];

        return $this->call('corpstate', 'RegistCorp', $params);
    }

    /**
     * 회원사 정보 수정 (UpdateCorpInfo)
     */
    public function updateCorpInfo(array $data): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($data['biz_no']),
            'CorpName' => $data['corp_name'],
            'CEOName' => $data['ceo_name'],
            'BizType' => $data['biz_type'] ?? '',
            'BizClass' => $data['biz_class'] ?? '',
            'PostNum' => $data['post_num'] ?? '',
            'Addr1' => $data['addr'] ?? '',
            'Addr2' => '',
            'MemberName' => $data['manager_name'] ?? '',
            'TEL' => $data['tel'] ?? '',
            'HP' => $data['manager_hp'] ?? '',
            'Email' => $data['manager_email'] ?? '',
        ];

        return $this->call('corpstate', 'UpdateCorpInfo', $params);
    }

    /**
     * 회원사 상태 조회 (GetCorpState)
     */
    public function getCorpState(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('corpstate', 'GetCorpState', $params);
    }

    /**
     * 사업자번호 포맷팅 (하이픈 제거)
     */
    protected function formatBizNo(string $bizNo): string
    {
        return preg_replace('/[^0-9]/', '', $bizNo);
    }

    // ========================================
    // 계좌조회 서비스 (BANKACCOUNT)
    // ========================================

    /**
     * 계좌조회 신청 URL 조회
     *
     * 회원사가 계좌를 등록할 수 있는 바로빌 페이지 URL을 반환합니다.
     * 이 URL로 리다이렉트하면 사용자가 직접 계좌를 등록할 수 있습니다.
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getBankAccountScrapRequestUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('bankaccount', 'GetBankAccountScrapRequestURL', $params);
    }

    /**
     * 계좌번호 관리 URL 조회
     *
     * 회원사가 등록된 계좌를 관리할 수 있는 바로빌 페이지 URL을 반환합니다.
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getBankAccountManagementUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('bankaccount', 'GetBankAccountManagementURL', $params);
    }

    /**
     * 입출금내역 URL 조회
     *
     * 회원사가 입출금내역을 조회할 수 있는 바로빌 페이지 URL을 반환합니다.
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getBankAccountLogUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('bankaccount', 'GetBankAccountLogURL', $params);
    }

    /**
     * 인증서 등록 URL 조회
     *
     * 회원사가 공동인증서를 등록할 수 있는 바로빌 페이지 URL을 반환합니다.
     * 세금계산서 발행 시 필요합니다.
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getCertificateRegistUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('bankaccount', 'GetCertificateRegistURL', $params);
    }

    /**
     * 인증서 등록일 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     */
    public function getCertificateRegistDate(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('bankaccount', 'GetCertificateRegistDate', $params);
    }

    /**
     * 인증서 만료일 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     */
    public function getCertificateExpireDate(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('bankaccount', 'GetCertificateExpireDate', $params);
    }

    /**
     * 인증서 유효성 확인
     *
     * @param  string  $corpNum  회원사 사업자번호
     */
    public function checkCertificateValid(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('bankaccount', 'CheckCERTIsValid', $params);
    }

    /**
     * 등록계좌 목록 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  bool  $availOnly  사용 가능한 계좌만 조회 여부
     */
    public function getBankAccounts(string $corpNum, bool $availOnly = true): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'AvailOnly' => $availOnly,
        ];

        return $this->call('bankaccount', 'GetBankAccountEx', $params);
    }

    /**
     * 바로빌 URL 조회 (범용)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     * @param  string  $togo  이동할 페이지 코드
     */
    public function getBarobillUrl(string $corpNum, string $userId, string $userPwd, string $togo = ''): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
            'TOGO' => $togo,
        ];

        return $this->call('bankaccount', 'GetBaroBillURL', $params);
    }

    /**
     * 현금충전 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getCashChargeUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('bankaccount', 'GetCashChargeURL', $params);
    }

    /**
     * 회원사 충전잔액 확인
     *
     * @param  string  $corpNum  회원사 사업자번호
     */
    public function getBalanceCostAmount(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('bankaccount', 'GetBalanceCostAmountEx', $params);
    }

    /**
     * 설정 상태 확인
     */
    public function checkConfiguration(): array
    {
        $dbConfig = $this->loadConfigFromDatabase();

        return [
            'cert_key_set' => ! empty($this->certKey),
            'cert_key_test_set' => ! empty(config('services.barobill.cert_key_test')),
            'cert_key_prod_set' => ! empty(config('services.barobill.cert_key_prod')),
            'corp_num_set' => ! empty($this->corpNum),
            'test_mode' => $this->isTestMode,
            'mode_label' => $this->isTestMode ? '테스트' : '운영',
            'soap_url' => $this->soapUrls['corpstate'],
            'config_source' => $dbConfig ? 'database' : 'env',
            'db_config_name' => $dbConfig?->name,
        ];
    }

    // ========================================
    // 카드조회 서비스 (CARD)
    // ========================================

    /**
     * 카드사 코드 목록
     */
    public static array $cardCompanyCodes = [
        '01' => '비씨카드',
        '02' => '국민카드',
        '03' => '외환카드',
        '04' => '삼성카드',
        '05' => '현대카드',
        '06' => '롯데카드',
        '07' => '신한카드',
        '08' => '하나카드',
        '09' => '우리카드',
        '10' => 'NH농협카드',
        '11' => '씨티카드',
        '12' => '광주카드',
        '13' => '전북카드',
        '14' => '수협카드',
        '15' => '제주카드',
        '16' => '산업카드',
    ];

    /**
     * 카드 등록 (RegistCardEx)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  array  $cardData  카드 정보
     */
    public function registCard(string $corpNum, array $cardData): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'CardCompany' => $cardData['card_company'] ?? '',
            'CardType' => $cardData['card_type'] ?? '1', // 1: 법인, 2: 개인
            'CardNum' => preg_replace('/[^0-9]/', '', $cardData['card_num'] ?? ''),
            'WebId' => $cardData['web_id'] ?? '',
            'WebPwd' => $cardData['web_pwd'] ?? '',
            'Alias' => $cardData['alias'] ?? '',
            'Usage' => $cardData['usage'] ?? '',
            'CollectCycle' => $cardData['collect_cycle'] ?? '1', // 1: 1시간, 2: 3시간, 3: 6시간, 4: 12시간, 5: 24시간
        ];

        return $this->call('card', 'RegistCardEx', $params);
    }

    /**
     * 카드 목록 조회 (GetCardEx2)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  bool  $availOnly  사용 가능한 카드만 조회
     */
    public function getCards(string $corpNum, bool $availOnly = true): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'AvailOnly' => $availOnly ? 1 : 0,
        ];

        return $this->call('card', 'GetCardEx2', $params);
    }

    /**
     * 카드 정보 수정 (UpdateCard)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  array  $cardData  카드 정보
     */
    public function updateCard(string $corpNum, array $cardData): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'CardNum' => preg_replace('/[^0-9]/', '', $cardData['card_num'] ?? ''),
            'WebId' => $cardData['web_id'] ?? '',
            'WebPwd' => $cardData['web_pwd'] ?? '',
            'Alias' => $cardData['alias'] ?? '',
            'Usage' => $cardData['usage'] ?? '',
        ];

        return $this->call('card', 'UpdateCard', $params);
    }

    /**
     * 카드 해지 (StopCard)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $cardNum  카드번호
     */
    public function stopCard(string $corpNum, string $cardNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'CardNum' => preg_replace('/[^0-9]/', '', $cardNum),
        ];

        return $this->call('card', 'StopCard', $params);
    }

    /**
     * 카드 해지 취소 (CancelStopCard)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $cardNum  카드번호
     */
    public function cancelStopCard(string $corpNum, string $cardNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'CardNum' => preg_replace('/[^0-9]/', '', $cardNum),
        ];

        return $this->call('card', 'CancelStopCard', $params);
    }

    /**
     * 카드 일별 사용내역 조회 (GetDailyCardLog)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $id  회원사 ID
     * @param  string  $cardNum  카드번호 (빈 값이면 전체)
     * @param  string  $baseDate  기준일자 (YYYYMMDD)
     * @param  int  $countPerPage  페이지당 건수
     * @param  int  $currentPage  현재 페이지
     */
    public function getDailyCardLog(
        string $corpNum,
        string $id,
        string $cardNum,
        string $baseDate,
        int $countPerPage = 20,
        int $currentPage = 1
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
            'CardNum' => preg_replace('/[^0-9]/', '', $cardNum),
            'BaseDate' => $baseDate,
            'CountPerPage' => $countPerPage,
            'CurrentPage' => $currentPage,
            'OrderDirection' => 'D', // D: 내림차순, A: 오름차순
        ];

        return $this->call('card', 'GetDailyCardLog', $params);
    }

    /**
     * 카드 월별 사용내역 조회 (GetMonthlyCardLog)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $id  회원사 ID
     * @param  string  $cardNum  카드번호 (빈 값이면 전체)
     * @param  string  $baseMonth  기준월 (YYYYMM)
     * @param  int  $countPerPage  페이지당 건수
     * @param  int  $currentPage  현재 페이지
     */
    public function getMonthlyCardLog(
        string $corpNum,
        string $id,
        string $cardNum,
        string $baseMonth,
        int $countPerPage = 20,
        int $currentPage = 1
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
            'CardNum' => preg_replace('/[^0-9]/', '', $cardNum),
            'BaseMonth' => $baseMonth,
            'CountPerPage' => $countPerPage,
            'CurrentPage' => $currentPage,
            'OrderDirection' => 'D',
        ];

        return $this->call('card', 'GetMonthlyCardLog', $params);
    }

    /**
     * 카드 기간별 사용내역 조회 (GetPeriodCardLog)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $id  회원사 ID
     * @param  string  $cardNum  카드번호 (빈 값이면 전체)
     * @param  string  $startDate  시작일 (YYYYMMDD)
     * @param  string  $endDate  종료일 (YYYYMMDD)
     * @param  int  $countPerPage  페이지당 건수
     * @param  int  $currentPage  현재 페이지
     */
    public function getPeriodCardLog(
        string $corpNum,
        string $id,
        string $cardNum,
        string $startDate,
        string $endDate,
        int $countPerPage = 20,
        int $currentPage = 1
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
            'CardNum' => preg_replace('/[^0-9]/', '', $cardNum),
            'StartDate' => $startDate,
            'EndDate' => $endDate,
            'CountPerPage' => $countPerPage,
            'CurrentPage' => $currentPage,
            'OrderDirection' => 'D',
        ];

        return $this->call('card', 'GetPeriodCardLog', $params);
    }

    /**
     * 카드 등록 신청 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getCardScrapRequestUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('card', 'GetCardScrapRequestURL', $params);
    }

    /**
     * 카드 관리 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getCardManagementUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('card', 'GetCardManagementURL', $params);
    }

    /**
     * 카드 사용내역 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getCardLogUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('card', 'GetCardLogURL', $params);
    }

    // ========================================
    // 계좌조회 확장 (BANKACCOUNT)
    // ========================================

    /**
     * 은행 코드 목록
     */
    public static array $bankCodes = [
        '02' => '산업은행',
        '03' => '기업은행',
        '04' => '국민은행',
        '05' => '외환은행',
        '06' => '국민은행(구주택)',
        '07' => '수협은행',
        '08' => '수출입은행',
        '11' => 'NH농협은행',
        '12' => '지역농축협',
        '20' => '우리은행',
        '21' => '신한은행(구조흥)',
        '22' => '상업은행(구)',
        '23' => '제일은행(SC)',
        '25' => '하나은행(구 서울)',
        '26' => '신한은행',
        '27' => '한국씨티은행',
        '31' => '대구은행',
        '32' => '부산은행',
        '34' => '광주은행',
        '35' => '제주은행',
        '37' => '전북은행',
        '38' => '강원은행(구)',
        '39' => '경남은행',
        '41' => '비씨카드',
        '45' => '새마을금고연합',
        '48' => '신협',
        '50' => '상호저축은행',
        '53' => '한국씨티은행(구)',
        '54' => 'HSBC은행',
        '55' => '도이치은행',
        '56' => 'ABN-AMRO',
        '57' => 'JP모간',
        '59' => '미쓰비시도쿄은행',
        '60' => 'BOA',
        '64' => '산림조합중앙회',
        '71' => '우체국',
        '81' => 'KEB하나은행',
        '88' => '신한은행(통합)',
        '89' => '케이뱅크',
        '90' => '카카오뱅크',
        '92' => '토스뱅크',
    ];

    /**
     * 계좌 일별 입출금내역 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $id  회원사 ID
     * @param  string  $bankAccountNum  계좌번호 (빈 값이면 전체)
     * @param  string  $baseDate  기준일자 (YYYYMMDD)
     * @param  string  $transDirection  I: 입금, O: 출금, '' 전체
     * @param  int  $countPerPage  페이지당 건수
     * @param  int  $currentPage  현재 페이지
     */
    public function getDailyBankAccountTransLog(
        string $corpNum,
        string $id,
        string $bankAccountNum,
        string $baseDate,
        string $transDirection = '',
        int $countPerPage = 20,
        int $currentPage = 1
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
            'BankAccountNum' => $bankAccountNum,
            'BaseDate' => $baseDate,
            'TransDirection' => $transDirection,
            'CountPerPage' => $countPerPage,
            'CurrentPage' => $currentPage,
            'OrderDirection' => 'D',
        ];

        return $this->call('bankaccount', 'GetDailyBankAccountTransLog', $params);
    }

    /**
     * 계좌 월별 입출금내역 조회
     */
    public function getMonthlyBankAccountTransLog(
        string $corpNum,
        string $id,
        string $bankAccountNum,
        string $baseMonth,
        string $transDirection = '',
        int $countPerPage = 20,
        int $currentPage = 1
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
            'BankAccountNum' => $bankAccountNum,
            'BaseMonth' => $baseMonth,
            'TransDirection' => $transDirection,
            'CountPerPage' => $countPerPage,
            'CurrentPage' => $currentPage,
            'OrderDirection' => 'D',
        ];

        return $this->call('bankaccount', 'GetMonthlyBankAccountTransLog', $params);
    }

    /**
     * 계좌 기간별 입출금내역 조회
     */
    public function getPeriodBankAccountTransLog(
        string $corpNum,
        string $id,
        string $bankAccountNum,
        string $startDate,
        string $endDate,
        string $transDirection = '',
        int $countPerPage = 20,
        int $currentPage = 1
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
            'BankAccountNum' => $bankAccountNum,
            'StartDate' => $startDate,
            'EndDate' => $endDate,
            'TransDirection' => $transDirection,
            'CountPerPage' => $countPerPage,
            'CurrentPage' => $currentPage,
            'OrderDirection' => 'D',
        ];

        return $this->call('bankaccount', 'GetPeriodBankAccountTransLog', $params);
    }

    // ========================================
    // 전자세금계산서 서비스 (TI)
    // ========================================

    /**
     * 세금계산서 발행 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getTaxInvoiceIssueUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('ti', 'GetTaxInvoiceIssueURL', $params);
    }

    /**
     * 세금계산서 목록 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getTaxInvoiceListUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('ti', 'GetTaxInvoiceListURL', $params);
    }

    /**
     * 매입/매출 관리 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $userId  회원사 사용자 ID
     * @param  string  $userPwd  회원사 사용자 비밀번호
     */
    public function getHomeTaxUrl(string $corpNum, string $userId, string $userPwd): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $userId,
            'PWD' => $userPwd,
        ];

        return $this->call('ti', 'GetHomeTaxURL', $params);
    }

    // ========================================
    // 카카오톡 서비스 (KAKAOTALK)
    // ========================================

    /**
     * KAKAOTALK SOAP 클라이언트 가져오기
     */
    protected function getKakaotalkClient(): ?SoapClient
    {
        if ($this->kakaotalkClient === null) {
            try {
                $this->kakaotalkClient = new SoapClient($this->soapUrls['kakaotalk'], [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (Throwable $e) {
                Log::error('바로빌 KAKAOTALK SOAP 클라이언트 생성 실패', [
                    'error' => $e->getMessage(),
                    'url' => $this->soapUrls['kakaotalk'],
                ]);

                return null;
            }
        }

        return $this->kakaotalkClient;
    }

    // --- 채널 관리 ---

    /**
     * 카카오톡 채널 목록 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     */
    public function getKakaotalkChannels(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('kakaotalk', 'GetKakaotalkChannels', $params);
    }

    /**
     * 카카오톡 채널 관리 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $id  회원사 사용자 ID
     */
    public function getKakaotalkChannelManagementUrl(string $corpNum, string $id): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
        ];

        return $this->call('kakaotalk', 'GetKakaotalkChannelManagementURL', $params);
    }

    // --- 템플릿 관리 ---

    /**
     * 카카오톡 템플릿 목록 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $channelId  카카오톡 채널 ID
     */
    public function getKakaotalkTemplates(string $corpNum, string $channelId): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ChannelId' => $channelId,
        ];

        return $this->call('kakaotalk', 'GetKakaotalkTemplates', $params);
    }

    /**
     * 카카오톡 템플릿 관리 URL 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $id  회원사 사용자 ID
     */
    public function getKakaotalkTemplateManagementUrl(string $corpNum, string $id): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'ID' => $id,
        ];

        return $this->call('kakaotalk', 'GetKakaotalkTemplateManagementURL', $params);
    }

    // --- 알림톡 발송 ---

    /**
     * 알림톡 단건 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  바로빌 계정 ID
     * @param  string  $yellowId  카카오톡 채널 ID (예: @codebridge-x)
     * @param  string  $templateName  템플릿 이름
     * @param  string  $receiverName  수신자 이름
     * @param  string  $receiverNum  수신자 전화번호
     * @param  string  $title  알림톡 제목
     * @param  string  $message  알림톡 본문
     * @param  string  $smsMessage  SMS 대체발송 메시지 (빈 값이면 대체발송 안 함)
     * @param  string  $smsSubject  SMS 대체발송 제목
     * @param  string  $smsSenderNum  SMS 발신번호
     * @param  string  $reserveDT  예약일시 (YYYYMMDDHHmmss, 빈 값이면 즉시발송)
     */
    public function sendATKakaotalk(
        string $corpNum,
        string $senderId,
        string $yellowId,
        string $templateName,
        string $receiverName,
        string $receiverNum,
        string $title,
        string $message,
        string $smsMessage = '',
        string $smsSubject = '',
        string $smsSenderNum = '',
        string $reserveDT = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'YellowId' => $yellowId,
            'TemplateName' => $templateName,
            'SendDT' => $reserveDT,
            'SmsReply' => (empty($smsMessage) || empty($smsSenderNum)) ? 'N' : 'S',
            'SmsSenderNum' => $smsSenderNum,
            'KakaotalkMessage' => [
                'ReceiverName' => $receiverName,
                'ReceiverNum' => $receiverNum,
                'Title' => $title,
                'Message' => $message,
                'SmsMessage' => $smsMessage,
                'SmsSubject' => $smsSubject,
            ],
        ];

        return $this->call('kakaotalk', 'SendATKakaotalk', $params);
    }

    /**
     * 알림톡 단건 발송 (버튼 포함)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  바로빌 계정 ID
     * @param  string  $yellowId  카카오톡 채널 ID (예: @codebridge-x)
     * @param  string  $templateName  템플릿 이름
     * @param  string  $receiverName  수신자 이름
     * @param  string  $receiverNum  수신자 전화번호
     * @param  string  $title  알림톡 제목
     * @param  string  $message  알림톡 본문
     * @param  array  $buttons  버튼 배열 [{Name, ButtonType, Url1, Url2}]
     * @param  string  $smsMessage  SMS 대체발송 메시지
     * @param  string  $smsSubject  SMS 대체발송 제목
     * @param  string  $smsSenderNum  SMS 발신번호
     * @param  string  $reserveDT  예약일시
     */
    public function sendATKakaotalkEx(
        string $corpNum,
        string $senderId,
        string $yellowId,
        string $templateName,
        string $receiverName,
        string $receiverNum,
        string $title,
        string $message,
        array $buttons = [],
        string $smsMessage = '',
        string $smsSubject = '',
        string $smsSenderNum = '',
        string $reserveDT = ''
    ): array {
        $kakaotalkMessage = [
            'ReceiverName' => $receiverName,
            'ReceiverNum' => $receiverNum,
            'Title' => $title,
            'Message' => $message,
            'SmsMessage' => $smsMessage,
            'SmsSubject' => $smsSubject,
        ];

        if (! empty($buttons)) {
            $kakaotalkMessage['Buttons'] = ['KakaotalkButton' => $buttons];
        }

        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'YellowId' => $yellowId,
            'TemplateName' => $templateName,
            'SendDT' => $reserveDT,
            'SmsReply' => (empty($smsMessage) || empty($smsSenderNum)) ? 'N' : 'S',
            'SmsSenderNum' => $smsSenderNum,
            'KakaotalkMessage' => $kakaotalkMessage,
        ];

        return $this->call('kakaotalk', 'SendATKakaotalkEx', $params);
    }

    /**
     * 알림톡 대량 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  바로빌 계정 ID
     * @param  string  $yellowId  카카오톡 채널 ID (예: @codebridge-x)
     * @param  string  $templateName  템플릿 이름
     * @param  array  $messages  메시지 배열 [{ReceiverName, ReceiverNum, Title, Message, SmsMessage, SmsSubject}]
     * @param  string  $reserveDT  예약일시
     */
    public function sendATKakaotalks(
        string $corpNum,
        string $senderId,
        string $yellowId,
        string $templateName,
        array $messages,
        string $reserveDT = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'YellowId' => $yellowId,
            'TemplateName' => $templateName,
            'SendDT' => $reserveDT,
            'SmsReply' => 'N',
            'SmsSenderNum' => '',
            'KakaotalkMessages' => ['KakaotalkATMessage' => $messages],
        ];

        return $this->call('kakaotalk', 'SendATKakaotalks', $params);
    }

    // --- 친구톡 발송 ---

    /**
     * 친구톡 텍스트 단건 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  발신프로필 키
     * @param  string  $channelId  채널 ID
     * @param  string  $receiverName  수신자 이름
     * @param  string  $receiverNum  수신자 전화번호
     * @param  string  $message  메시지 내용
     * @param  array  $buttons  버튼 배열
     * @param  string  $smsMessage  SMS 대체발송 메시지
     * @param  string  $smsSubject  SMS 대체발송 제목
     * @param  bool  $adYn  광고 여부
     * @param  string  $reserveDT  예약일시
     */
    public function sendFTKakaotalk(
        string $corpNum,
        string $senderId,
        string $channelId,
        string $receiverName,
        string $receiverNum,
        string $message,
        array $buttons = [],
        string $smsMessage = '',
        string $smsSubject = '',
        bool $adYn = false,
        string $reserveDT = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'ChannelId' => $channelId,
            'ReceiverName' => $receiverName,
            'ReceiverNum' => $receiverNum,
            'Message' => $message,
            'Buttons' => $buttons,
            'SmsMessage' => $smsMessage,
            'SmsSubject' => $smsSubject,
            'AdYn' => $adYn ? 'Y' : 'N',
            'ReserveDT' => $reserveDT,
        ];

        return $this->call('kakaotalk', 'SendFTKakaotalk', $params);
    }

    /**
     * 친구톡 대량 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  발신프로필 키
     * @param  string  $channelId  채널 ID
     * @param  array  $messages  메시지 배열
     * @param  bool  $adYn  광고 여부
     * @param  string  $reserveDT  예약일시
     */
    public function sendFTKakaotalks(
        string $corpNum,
        string $senderId,
        string $channelId,
        array $messages,
        bool $adYn = false,
        string $reserveDT = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'ChannelId' => $channelId,
            'Messages' => $messages,
            'AdYn' => $adYn ? 'Y' : 'N',
            'ReserveDT' => $reserveDT,
        ];

        return $this->call('kakaotalk', 'SendFTKakaotalks', $params);
    }

    /**
     * 친구톡 이미지 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  발신프로필 키
     * @param  string  $channelId  채널 ID
     * @param  string  $receiverName  수신자 이름
     * @param  string  $receiverNum  수신자 전화번호
     * @param  string  $message  메시지 내용
     * @param  string  $imageUrl  이미지 URL
     * @param  array  $buttons  버튼 배열
     * @param  string  $smsMessage  SMS 대체발송 메시지
     * @param  string  $smsSubject  SMS 대체발송 제목
     * @param  bool  $adYn  광고 여부
     * @param  string  $reserveDT  예약일시
     */
    public function sendFIKakaotalk(
        string $corpNum,
        string $senderId,
        string $channelId,
        string $receiverName,
        string $receiverNum,
        string $message,
        string $imageUrl,
        array $buttons = [],
        string $smsMessage = '',
        string $smsSubject = '',
        bool $adYn = false,
        string $reserveDT = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'ChannelId' => $channelId,
            'ReceiverName' => $receiverName,
            'ReceiverNum' => $receiverNum,
            'Message' => $message,
            'ImageURL' => $imageUrl,
            'Buttons' => $buttons,
            'SmsMessage' => $smsMessage,
            'SmsSubject' => $smsSubject,
            'AdYn' => $adYn ? 'Y' : 'N',
            'ReserveDT' => $reserveDT,
        ];

        return $this->call('kakaotalk', 'SendFIKakaotalk', $params);
    }

    /**
     * 친구톡 와이드 이미지 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  발신프로필 키
     * @param  string  $channelId  채널 ID
     * @param  string  $receiverName  수신자 이름
     * @param  string  $receiverNum  수신자 전화번호
     * @param  string  $message  메시지 내용
     * @param  string  $imageUrl  와이드 이미지 URL
     * @param  array  $buttons  버튼 배열
     * @param  string  $smsMessage  SMS 대체발송 메시지
     * @param  string  $smsSubject  SMS 대체발송 제목
     * @param  bool  $adYn  광고 여부
     * @param  string  $reserveDT  예약일시
     */
    public function sendFWKakaotalk(
        string $corpNum,
        string $senderId,
        string $channelId,
        string $receiverName,
        string $receiverNum,
        string $message,
        string $imageUrl,
        array $buttons = [],
        string $smsMessage = '',
        string $smsSubject = '',
        bool $adYn = false,
        string $reserveDT = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'ChannelId' => $channelId,
            'ReceiverName' => $receiverName,
            'ReceiverNum' => $receiverNum,
            'Message' => $message,
            'ImageURL' => $imageUrl,
            'Buttons' => $buttons,
            'SmsMessage' => $smsMessage,
            'SmsSubject' => $smsSubject,
            'AdYn' => $adYn ? 'Y' : 'N',
            'ReserveDT' => $reserveDT,
        ];

        return $this->call('kakaotalk', 'SendFWKakaotalk', $params);
    }

    // --- 전송 조회/관리 ---

    /**
     * 전송 결과 조회 (단건)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $sendKey  전송키
     */
    public function getSendKakaotalk(string $corpNum, string $sendKey): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SendKey' => $sendKey,
        ];

        return $this->call('kakaotalk', 'GetSendKakaotalk', $params);
    }

    /**
     * 전송 결과 조회 (다건)
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  array  $sendKeyList  전송키 배열
     */
    public function getSendKakaotalks(string $corpNum, array $sendKeyList): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SendKeyList' => $sendKeyList,
        ];

        return $this->call('kakaotalk', 'GetSendKakaotalks', $params);
    }

    /**
     * 예약 전송 취소
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $sendKey  전송키
     */
    public function cancelReservedKakaotalk(string $corpNum, string $sendKey): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SendKey' => $sendKey,
        ];

        return $this->call('kakaotalk', 'CancelReservedKakaotalk', $params);
    }

    // ========================================
    // 문자 서비스 (SMS)
    // ========================================

    /**
     * SMS SOAP 클라이언트 가져오기
     */
    protected function getSmsClient(): ?SoapClient
    {
        if ($this->smsClient === null) {
            try {
                $this->smsClient = new SoapClient($this->soapUrls['sms'], [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (Throwable $e) {
                Log::error('바로빌 SMS SOAP 클라이언트 생성 실패', [
                    'error' => $e->getMessage(),
                    'url' => $this->soapUrls['sms'],
                ]);

                return null;
            }
        }

        return $this->smsClient;
    }

    /**
     * SMS 단문 발송
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $senderId  바로빌 계정 ID
     * @param  string  $fromNumber  발신번호
     * @param  string  $toName  수신자명
     * @param  string  $toNumber  수신 전화번호
     * @param  string  $contents  메시지 내용 (90바이트 이내)
     * @param  string  $sendDT  예약일시 (빈값 = 즉시발송)
     * @param  string  $refKey  참조키
     */
    public function sendSMSMessage(
        string $corpNum,
        string $senderId,
        string $fromNumber,
        string $toName,
        string $toNumber,
        string $contents,
        string $sendDT = '',
        string $refKey = ''
    ): array {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SenderID' => $senderId,
            'FromNumber' => $fromNumber,
            'ToName' => $toName,
            'ToNumber' => $toNumber,
            'Contents' => $contents,
            'SendDT' => $sendDT,
            'RefKey' => $refKey,
        ];

        return $this->call('sms', 'SendSMSMessage', $params);
    }

    /**
     * 발신번호 등록 여부 확인
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $fromNumber  확인할 발신번호
     */
    public function checkSMSFromNumber(string $corpNum, string $fromNumber): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'FromNumber' => $fromNumber,
        ];

        return $this->call('sms', 'CheckSMSFromNumber', $params);
    }

    /**
     * 등록된 발신번호 목록 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     */
    public function getSMSFromNumbers(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

        return $this->call('sms', 'GetSMSFromNumbers', $params);
    }

    /**
     * SMS 전송 상태 조회
     *
     * @param  string  $corpNum  회원사 사업자번호
     * @param  string  $sendKey  전송키
     */
    public function getSMSSendState(string $corpNum, string $sendKey): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'SendKey' => $sendKey,
        ];

        return $this->call('sms', 'GetSMSSendState', $params);
    }
}
