<?php

namespace App\Services\Barobill;

use App\Models\Tenants\BarobillConfig;
use App\Models\Tenants\BarobillMember;
use App\Services\Service;
use Illuminate\Support\Facades\Log;
use SoapClient;
use SoapFault;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Throwable;

class BarobillSoapService extends Service
{
    protected ?SoapClient $corpStateClient = null;

    protected ?SoapClient $bankAccountClient = null;

    protected ?SoapClient $cardClient = null;

    protected string $certKey;

    protected string $corpNum;

    protected bool $isTestMode;

    protected array $soapUrls;

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

    public function __construct()
    {
        $this->isTestMode = config('services.barobill.test_mode', true);
        $this->initializeConfig();
    }

    public function switchServerMode(bool $isTestMode): self
    {
        if ($this->isTestMode !== $isTestMode) {
            $this->isTestMode = $isTestMode;
            $this->corpStateClient = null;
            $this->bankAccountClient = null;
            $this->cardClient = null;
            $this->initializeConfig();
        }

        return $this;
    }

    protected function initializeConfig(): void
    {
        $dbConfig = $this->loadConfigFromDatabase();

        if ($dbConfig) {
            $this->certKey = $dbConfig->cert_key;
            $this->corpNum = $dbConfig->corp_num ?? '';
            $this->soapUrls = $this->buildSoapUrls($dbConfig->base_url);
        } else {
            $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 = $this->buildSoapUrls($baseUrl);
        }
    }

    protected function loadConfigFromDatabase(): ?BarobillConfig
    {
        try {
            return BarobillConfig::getActive($this->isTestMode);
        } catch (\Exception $e) {
            Log::warning('바로빌 DB 설정 로드 실패', ['error' => $e->getMessage()]);

            return null;
        }
    }

    protected function buildSoapUrls(string $baseUrl): array
    {
        $baseUrl = rtrim($baseUrl, '/');

        return [
            'corpstate' => $baseUrl.'/CORPSTATE.asmx?WSDL',
            'bankaccount' => $baseUrl.'/BANKACCOUNT.asmx?WSDL',
            'card' => $baseUrl.'/CARD.asmx?WSDL',
        ];
    }

    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;
    }

    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;
    }

    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;
    }

    protected function call(string $service, string $method, array $params = []): array
    {
        $client = match ($service) {
            'corpstate' => $this->getCorpStateClient(),
            'bankaccount' => $this->getBankAccountClient(),
            'card' => $this->getCardClient(),
            default => null,
        };

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

        if (empty($this->certKey)) {
            return [
                'success' => false,
                'error' => 'CERTKEY가 설정되지 않았습니다.',
                'error_code' => -2,
            ];
        }

        if (! isset($params['CERTKEY'])) {
            $params['CERTKEY'] = $this->certKey;
        }

        try {
            Log::info('바로빌 SOAP 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('바로빌 SOAP API 오류', [
                        'method' => $method,
                        'error_code' => $resultData,
                        'error_message' => $errorMessage,
                    ]);

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

                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('바로빌 SOAP API 호출 오류', [
                'method' => $method,
                'error' => $e->getMessage(),
            ]);

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

    protected function formatBizNo(string $bizNo): string
    {
        return preg_replace('/[^0-9]/', '', $bizNo);
    }

    // =========================================================================
    // 회원사 조회
    // =========================================================================

    protected function getMember(): BarobillMember
    {
        $tenantId = $this->tenantId();
        $member = BarobillMember::where('tenant_id', $tenantId)->first();

        if (! $member) {
            throw new BadRequestHttpException(__('error.barobill.member_not_found'));
        }

        $this->switchServerMode($member->isTestMode());

        return $member;
    }

    // =========================================================================
    // SOAP 비즈니스 메서드
    // =========================================================================

    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',
            'TEL' => $data['tel'] ?? '',
            'HP' => $data['manager_hp'] ?? '',
            'Email' => $data['manager_email'] ?? '',
        ];

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

    public function getCorpState(string $corpNum): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
        ];

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

    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);
    }

    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);
    }

    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);
    }

    public function getBankAccounts(string $corpNum, bool $availOnly = true): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'AvailOnly' => $availOnly,
        ];

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

    public function getCards(string $corpNum, bool $availOnly = true): array
    {
        $params = [
            'CorpNum' => $this->formatBizNo($corpNum),
            'AvailOnly' => $availOnly ? 1 : 0,
        ];

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

    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);
    }

    // =========================================================================
    // 컨트롤러 비즈니스 메서드
    // =========================================================================

    public function registerLogin(array $data): array
    {
        $tenantId = $this->tenantId();
        $barobillId = $data['barobill_id'];
        $password = $data['password'];

        $member = BarobillMember::where('tenant_id', $tenantId)->first();

        if ($member) {
            $this->switchServerMode($member->isTestMode());
        }

        $result = $this->getCorpState($member?->biz_no ?? config('services.barobill.corp_num'));

        if (! $result['success']) {
            throw new BadRequestHttpException($result['error']);
        }

        $member = BarobillMember::updateOrCreate(
            ['tenant_id' => $tenantId],
            [
                'barobill_id' => $barobillId,
                'barobill_pwd' => $password,
                'status' => 'active',
            ]
        );

        return [
            'id' => $member->id,
            'barobill_id' => $member->barobill_id,
            'status' => $member->status,
        ];
    }

    public function registerSignup(array $data): array
    {
        $tenantId = $this->tenantId();

        $soapResult = $this->registCorp([
            'biz_no' => $data['business_number'],
            'corp_name' => $data['company_name'],
            'ceo_name' => $data['ceo_name'],
            'biz_type' => $data['business_type'] ?? '',
            'biz_class' => $data['business_category'] ?? '',
            'addr' => $data['address'] ?? '',
            'barobill_id' => $data['barobill_id'],
            'barobill_pwd' => $data['password'],
            'manager_name' => $data['manager_name'] ?? '',
            'manager_hp' => $data['manager_phone'] ?? '',
            'manager_email' => $data['manager_email'] ?? '',
        ]);

        if (! $soapResult['success']) {
            throw new BadRequestHttpException($soapResult['error']);
        }

        $member = BarobillMember::updateOrCreate(
            ['tenant_id' => $tenantId],
            [
                'biz_no' => $this->formatBizNo($data['business_number']),
                'corp_name' => $data['company_name'],
                'ceo_name' => $data['ceo_name'],
                'biz_type' => $data['business_type'] ?? '',
                'biz_class' => $data['business_category'] ?? '',
                'addr' => $data['address'] ?? '',
                'barobill_id' => $data['barobill_id'],
                'barobill_pwd' => $data['password'],
                'manager_name' => $data['manager_name'] ?? '',
                'manager_hp' => $data['manager_phone'] ?? '',
                'manager_email' => $data['manager_email'] ?? '',
                'status' => 'active',
                'server_mode' => $this->isTestMode ? 'test' : 'production',
            ]
        );

        return [
            'id' => $member->id,
            'barobill_id' => $member->barobill_id,
            'biz_no' => $member->formatted_biz_no,
            'status' => $member->status,
        ];
    }

    public function getBankServiceRedirectUrl(array $params): array
    {
        $member = $this->getMember();

        $result = $this->getBankAccountLogUrl(
            $member->biz_no,
            $member->barobill_id,
            $member->barobill_pwd
        );

        if (! $result['success']) {
            throw new BadRequestHttpException($result['error']);
        }

        return ['url' => $result['data']];
    }

    public function getIntegrationStatus(): array
    {
        $member = $this->getMember();

        $bankResult = $this->getBankAccounts($member->biz_no);
        $cardResult = $this->getCards($member->biz_no);

        $bankCount = 0;
        if ($bankResult['success'] && isset($bankResult['data'])) {
            $data = $bankResult['data'];
            if (is_object($data) && isset($data->BankAccountInfo)) {
                $info = $data->BankAccountInfo;
                $bankCount = is_array($info) ? count($info) : 1;
            } elseif (is_array($data)) {
                $bankCount = count($data);
            }
        }

        $cardCount = 0;
        if ($cardResult['success'] && isset($cardResult['data'])) {
            $data = $cardResult['data'];
            if (is_object($data) && isset($data->CardInfo)) {
                $info = $data->CardInfo;
                $cardCount = is_array($info) ? count($info) : 1;
            } elseif (is_array($data)) {
                $cardCount = count($data);
            }
        }

        return [
            'bank_account_count' => $bankCount,
            'card_count' => $cardCount,
            'member' => [
                'barobill_id' => $member->barobill_id,
                'biz_no' => $member->formatted_biz_no,
                'status' => $member->status,
                'server_mode' => $member->server_mode,
            ],
        ];
    }

    public function getAccountLinkRedirectUrl(): array
    {
        $member = $this->getMember();

        $result = $this->getBankAccountScrapRequestUrl(
            $member->biz_no,
            $member->barobill_id,
            $member->barobill_pwd
        );

        if (! $result['success']) {
            throw new BadRequestHttpException($result['error']);
        }

        return ['url' => $result['data']];
    }

    public function getCardLinkRedirectUrl(): array
    {
        $member = $this->getMember();

        $result = $this->getCardScrapRequestUrl(
            $member->biz_no,
            $member->barobill_id,
            $member->barobill_pwd
        );

        if (! $result['success']) {
            throw new BadRequestHttpException($result['error']);
        }

        return ['url' => $result['data']];
    }

    public function getCertificateRedirectUrl(): array
    {
        $member = $this->getMember();

        $result = $this->getCertificateRegistUrl(
            $member->biz_no,
            $member->barobill_id,
            $member->barobill_pwd
        );

        if (! $result['success']) {
            throw new BadRequestHttpException($result['error']);
        }

        return ['url' => $result['data']];
    }
}
