<?php

namespace App\Services;

use App\Models\Tenants\BarobillSetting;
use App\Models\Tenants\TaxInvoice;
use Illuminate\Support\Facades\Log;
use SoapClient;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * 바로빌 API 연동 서비스 (SOAP)
 *
 * 바로빌 개발자센터: https://dev.barobill.co.kr/
 * 바로빌은 SOAP API만 제공하므로 SoapClient를 사용합니다.
 */
class BarobillService extends Service
{
    /**
     * 바로빌 SOAP 기본 URL
     */
    private const SOAP_BASE_URL = 'https://ws.baroservice.com';

    /**
     * 바로빌 SOAP 테스트 URL
     */
    private const SOAP_TEST_URL = 'https://testws.baroservice.com';

    /**
     * 테스트 모드 여부
     */
    private bool $testMode;

    /**
     * TI(Tax Invoice) SOAP 클라이언트
     */
    private ?SoapClient $tiSoapClient = null;

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

    // =========================================================================
    // SOAP 클라이언트
    // =========================================================================

    /**
     * TI SOAP 클라이언트 초기화/반환
     */
    private function getTiSoapClient(): SoapClient
    {
        if ($this->tiSoapClient === null) {
            $baseUrl = $this->testMode ? self::SOAP_TEST_URL : self::SOAP_BASE_URL;

            $context = stream_context_create([
                'ssl' => [
                    'verify_peer' => ! $this->testMode,
                    'verify_peer_name' => ! $this->testMode,
                    'allow_self_signed' => $this->testMode,
                ],
            ]);

            $this->tiSoapClient = new SoapClient($baseUrl.'/TI.asmx?WSDL', [
                'trace' => true,
                'encoding' => 'UTF-8',
                'exceptions' => true,
                'connection_timeout' => 30,
                'stream_context' => $context,
                'cache_wsdl' => WSDL_CACHE_NONE,
            ]);
        }

        return $this->tiSoapClient;
    }

    /**
     * SOAP API 호출
     *
     * MNG EtaxController::callBarobillSOAP() 포팅
     * 음수 반환값 = 에러 코드 (바로빌 규격)
     */
    private function callSoap(string $method, array $params): array
    {
        $client = $this->getTiSoapClient();

        if (! isset($params['CERTKEY'])) {
            $setting = $this->getSetting();
            if (! $setting) {
                return [
                    'success' => false,
                    'error' => '바로빌 설정이 없습니다.',
                ];
            }
            $params['CERTKEY'] = $setting->cert_key;
        }

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

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

                // 바로빌 규격: 음수 반환값은 에러 코드
                if (is_numeric($resultData) && $resultData < 0) {
                    return [
                        'success' => false,
                        'error' => '바로빌 API 오류 코드: '.$resultData,
                        'error_code' => (int) $resultData,
                    ];
                }

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

            return ['success' => true, 'data' => $result];
        } catch (\SoapFault $e) {
            return [
                'success' => false,
                'error' => 'SOAP 오류: '.$e->getMessage(),
            ];
        } catch (\Throwable $e) {
            return [
                'success' => false,
                'error' => 'API 호출 오류: '.$e->getMessage(),
            ];
        }
    }

    // =========================================================================
    // 설정 관리
    // =========================================================================

    /**
     * 바로빌 설정 조회
     */
    public function getSetting(): ?BarobillSetting
    {
        $tenantId = $this->tenantId();

        return BarobillSetting::query()
            ->where('tenant_id', $tenantId)
            ->first();
    }

    /**
     * 바로빌 설정 저장
     */
    public function saveSetting(array $data): BarobillSetting
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        $setting = BarobillSetting::query()
            ->where('tenant_id', $tenantId)
            ->first();

        if ($setting) {
            $setting->fill(array_merge($data, ['updated_by' => $userId]));
            $setting->save();
        } else {
            $setting = BarobillSetting::create(array_merge($data, [
                'tenant_id' => $tenantId,
                'created_by' => $userId,
                'updated_by' => $userId,
            ]));
        }

        return $setting->fresh();
    }

    /**
     * 연동 테스트 (SOAP)
     */
    public function testConnection(): array
    {
        $setting = $this->getSetting();

        if (! $setting || ! $setting->canConnect()) {
            throw new BadRequestHttpException(__('error.barobill.setting_not_configured'));
        }

        try {
            $response = $this->callSoap('GetAccessToken', [
                'CERTKEY' => $setting->cert_key,
                'CorpNum' => $setting->corp_num,
                'ID' => $setting->barobill_id,
            ]);

            if ($response['success']) {
                $resultData = $response['data'];

                // 양수 또는 문자열 토큰 = 성공
                if (is_string($resultData) || (is_numeric($resultData) && $resultData > 0)) {
                    $setting->verified_at = now();
                    $setting->save();

                    return [
                        'success' => true,
                        'message' => __('message.barobill.connection_success'),
                        'verified_at' => $setting->verified_at->toDateTimeString(),
                    ];
                }
            }

            throw new \Exception($response['error'] ?? __('error.barobill.connection_failed'));
        } catch (BadRequestHttpException $e) {
            throw $e;
        } catch (\Exception $e) {
            Log::error('바로빌 연동 테스트 실패', [
                'tenant_id' => $this->tenantId(),
                'error' => $e->getMessage(),
            ]);

            throw new BadRequestHttpException(__('error.barobill.connection_failed').': '.$e->getMessage());
        }
    }

    // =========================================================================
    // 사업자등록번호 검증
    // =========================================================================

    /**
     * 사업자등록번호 유효성 검사 (휴폐업 조회)
     *
     * 바로빌 API를 통해 사업자등록번호의 유효성을 검증합니다.
     * 바로빌 설정이 없는 경우 기본 형식 검증만 수행합니다.
     *
     * @param  string  $businessNumber  사업자등록번호 (10자리, 하이픈 제거)
     * @return array{valid: bool, status: string, status_label: string, corp_name: ?string, ceo_name: ?string, message: string}
     */
    public function checkBusinessNumber(string $businessNumber): array
    {
        // 하이픈 제거 및 숫자만 추출
        $businessNumber = preg_replace('/[^0-9]/', '', $businessNumber);

        // 기본 형식 검증 (10자리)
        if (strlen($businessNumber) !== 10) {
            return [
                'valid' => false,
                'status' => 'invalid_format',
                'status_label' => '형식 오류',
                'corp_name' => null,
                'ceo_name' => null,
                'message' => __('error.company.invalid_business_number_format'),
            ];
        }

        // 체크섬 검증 (사업자등록번호 자체 유효성)
        if (! $this->validateBusinessNumberChecksum($businessNumber)) {
            return [
                'valid' => false,
                'status' => 'invalid_checksum',
                'status_label' => '유효하지 않음',
                'corp_name' => null,
                'ceo_name' => null,
                'message' => __('error.company.invalid_business_number'),
            ];
        }

        // 바로빌 SOAP API 조회 시도
        try {
            $response = $this->callSoap('CheckCorpNum', [
                'CorpNum' => $businessNumber,
            ]);

            if ($response['success']) {
                $resultData = $response['data'];

                // 양수 결과 = 유효한 사업자
                if (is_numeric($resultData) && $resultData >= 0) {
                    return [
                        'valid' => true,
                        'status' => 'active',
                        'status_label' => '유효함',
                        'corp_name' => null,
                        'ceo_name' => null,
                        'message' => __('message.company.business_number_valid'),
                    ];
                }
            }

            // API 실패 시 형식 검증 결과만 반환
            return [
                'valid' => true,
                'status' => 'format_valid',
                'status_label' => '형식 유효',
                'corp_name' => null,
                'ceo_name' => null,
                'message' => __('message.company.business_number_format_valid'),
            ];
        } catch (\Exception $e) {
            // API 호출 실패 시 형식 검증 결과만 반환
            Log::warning('바로빌 사업자번호 조회 실패', [
                'business_number' => $businessNumber,
                'error' => $e->getMessage(),
            ]);

            return [
                'valid' => true,
                'status' => 'format_valid',
                'status_label' => '형식 유효 (API 조회 불가)',
                'corp_name' => null,
                'ceo_name' => null,
                'message' => __('message.company.business_number_format_valid'),
            ];
        }
    }

    /**
     * 사업자등록번호 체크섬 검증
     *
     * @param  string  $businessNumber  10자리 사업자등록번호
     */
    private function validateBusinessNumberChecksum(string $businessNumber): bool
    {
        if (strlen($businessNumber) !== 10) {
            return false;
        }

        $digits = str_split($businessNumber);
        $multipliers = [1, 3, 7, 1, 3, 7, 1, 3, 5];
        $sum = 0;

        for ($i = 0; $i < 9; $i++) {
            $sum += intval($digits[$i]) * $multipliers[$i];
        }

        // 8번째 자리 (인덱스 8)에 대한 추가 처리
        $sum += intval(floor(intval($digits[8]) * 5 / 10));

        $remainder = $sum % 10;
        $checkDigit = (10 - $remainder) % 10;

        return $checkDigit === intval($digits[9]);
    }

    // =========================================================================
    // 세금계산서 발행
    // =========================================================================

    /**
     * 세금계산서 발행 (SOAP RegistAndIssueTaxInvoice)
     *
     * MNG EtaxController::issueTaxInvoice() 포팅
     */
    public function issueTaxInvoice(TaxInvoice $taxInvoice): TaxInvoice
    {
        $setting = $this->getSetting();

        if (! $setting || ! $setting->canConnect()) {
            throw new BadRequestHttpException(__('error.barobill.setting_not_configured'));
        }

        try {
            $apiData = $this->buildTaxInvoiceData($taxInvoice, $setting);
            $response = $this->callSoap('RegistAndIssueTaxInvoice', $apiData);

            if ($response['success']) {
                $resultData = $response['data'];
                // 바로빌 규격: 양수 반환값이 Invoice ID
                $taxInvoice->barobill_invoice_id = is_numeric($resultData) ? (string) $resultData : null;
                $taxInvoice->status = TaxInvoice::STATUS_ISSUED;
                $taxInvoice->issued_at = now();
                $taxInvoice->error_message = null;
                $taxInvoice->save();

                Log::info('세금계산서 발행 성공', [
                    'tenant_id' => $this->tenantId(),
                    'tax_invoice_id' => $taxInvoice->id,
                    'barobill_invoice_id' => $taxInvoice->barobill_invoice_id,
                ]);

                return $taxInvoice->fresh();
            }

            throw new \Exception($response['error'] ?? '발행 실패');
        } catch (BadRequestHttpException $e) {
            throw $e;
        } catch (\Exception $e) {
            // 발행 실패
            $taxInvoice->status = TaxInvoice::STATUS_FAILED;
            $taxInvoice->error_message = $e->getMessage();
            $taxInvoice->save();

            Log::error('세금계산서 발행 실패', [
                'tenant_id' => $this->tenantId(),
                'tax_invoice_id' => $taxInvoice->id,
                'error' => $e->getMessage(),
            ]);

            throw new BadRequestHttpException(__('error.barobill.issue_failed').': '.$e->getMessage());
        }
    }

    /**
     * 세금계산서 취소 (SOAP CancelTaxInvoice)
     */
    public function cancelTaxInvoice(TaxInvoice $taxInvoice, string $reason): TaxInvoice
    {
        $setting = $this->getSetting();

        if (! $setting || ! $setting->canConnect()) {
            throw new BadRequestHttpException(__('error.barobill.setting_not_configured'));
        }

        if (! $taxInvoice->canCancel()) {
            throw new BadRequestHttpException(__('error.barobill.cannot_cancel'));
        }

        try {
            $response = $this->callSoap('ProcTaxInvoice', [
                'CERTKEY' => $setting->cert_key,
                'CorpNum' => $setting->corp_num,
                'MgtNum' => $taxInvoice->barobill_invoice_id,
                'ProcType' => 4, // 4: 발행취소
                'Memo' => $reason,
            ]);

            if ($response['success']) {
                $taxInvoice->status = TaxInvoice::STATUS_CANCELLED;
                $taxInvoice->cancelled_at = now();
                $taxInvoice->description = ($taxInvoice->description ? $taxInvoice->description."\n" : '').'취소 사유: '.$reason;
                $taxInvoice->save();

                Log::info('세금계산서 취소 성공', [
                    'tenant_id' => $this->tenantId(),
                    'tax_invoice_id' => $taxInvoice->id,
                    'reason' => $reason,
                ]);

                return $taxInvoice->fresh();
            }

            throw new \Exception($response['error'] ?? '취소 실패');
        } catch (BadRequestHttpException $e) {
            throw $e;
        } catch (\Exception $e) {
            Log::error('세금계산서 취소 실패', [
                'tenant_id' => $this->tenantId(),
                'tax_invoice_id' => $taxInvoice->id,
                'error' => $e->getMessage(),
            ]);

            throw new BadRequestHttpException(__('error.barobill.cancel_failed').': '.$e->getMessage());
        }
    }

    /**
     * 국세청 전송 상태 조회 (SOAP GetTaxInvoiceState)
     */
    public function checkNtsSendStatus(TaxInvoice $taxInvoice): TaxInvoice
    {
        $setting = $this->getSetting();

        if (! $setting || ! $setting->canConnect()) {
            throw new BadRequestHttpException(__('error.barobill.setting_not_configured'));
        }

        if (empty($taxInvoice->barobill_invoice_id)) {
            throw new BadRequestHttpException(__('error.barobill.not_issued'));
        }

        try {
            $response = $this->callSoap('GetTaxInvoiceState', [
                'CERTKEY' => $setting->cert_key,
                'CorpNum' => $setting->corp_num,
                'MgtNum' => $taxInvoice->barobill_invoice_id,
            ]);

            if ($response['success'] && $response['data']) {
                $stateData = $response['data'];

                // SOAP 결과 객체에서 상태 추출
                $state = is_object($stateData) ? ($stateData->NTSSendState ?? null) : null;

                if ($state !== null) {
                    $taxInvoice->nts_send_status = (string) $state;

                    // 국세청 전송 완료 시 상태 업데이트
                    if (in_array($state, [3, '3', '전송완료']) && ! $taxInvoice->sent_at) {
                        $taxInvoice->status = TaxInvoice::STATUS_SENT;
                        $taxInvoice->sent_at = now();

                        if (is_object($stateData) && ! empty($stateData->NTSConfirmNum)) {
                            $taxInvoice->nts_confirm_num = $stateData->NTSConfirmNum;
                        }
                    }

                    $taxInvoice->save();
                }
            }

            return $taxInvoice->fresh();
        } catch (BadRequestHttpException $e) {
            throw $e;
        } catch (\Exception $e) {
            Log::error('국세청 전송 상태 조회 실패', [
                'tenant_id' => $this->tenantId(),
                'tax_invoice_id' => $taxInvoice->id,
                'error' => $e->getMessage(),
            ]);

            throw new BadRequestHttpException(__('error.barobill.status_check_failed').': '.$e->getMessage());
        }
    }

    // =========================================================================
    // Private 메서드
    // =========================================================================

    /**
     * 세금계산서 발행용 데이터 구성 (MNG SOAP 형식)
     *
     * MNG EtaxController::issueTaxInvoice() 의 Invoice 구조를 포팅
     * InvoicerParty/InvoiceeParty/TaxInvoiceTradeLineItems 중첩 구조
     */
    private function buildTaxInvoiceData(TaxInvoice $taxInvoice, BarobillSetting $setting): array
    {
        $supplyAmt = (int) $taxInvoice->supply_amount;
        $taxAmt = (int) $taxInvoice->tax_amount;
        $total = (int) $taxInvoice->total_amount;
        $taxType = $taxAmt == 0 ? 2 : 1; // 1:과세, 2:영세, 3:면세

        // 관리번호 (유니크)
        $mgtNum = 'SAM'.date('YmdHis').$taxInvoice->id;

        // 품목 구성
        $tradeLineItems = [];
        foreach ($taxInvoice->items ?? [] as $item) {
            $tradeLineItems[] = [
                'PurchaseExpiry' => $taxInvoice->issue_date->format('Ymd'),
                'Name' => $item['name'] ?? '',
                'Information' => $item['spec'] ?? '',
                'ChargeableUnit' => (string) ($item['qty'] ?? 1),
                'UnitPrice' => (string) ($item['unit_price'] ?? 0),
                'Amount' => (string) ($item['supply_amt'] ?? 0),
                'Tax' => (string) ($item['tax_amt'] ?? 0),
                'Description' => $item['remark'] ?? '',
            ];
        }

        // 품목이 없는 경우 기본 품목 추가
        if (empty($tradeLineItems)) {
            $tradeLineItems[] = [
                'PurchaseExpiry' => $taxInvoice->issue_date->format('Ymd'),
                'Name' => $taxInvoice->description ?? '품목',
                'Information' => '',
                'ChargeableUnit' => '1',
                'UnitPrice' => (string) $supplyAmt,
                'Amount' => (string) $supplyAmt,
                'Tax' => (string) $taxAmt,
                'Description' => '',
            ];
        }

        return [
            'CorpNum' => $setting->corp_num,
            'Invoice' => [
                'IssueDirection' => 1, // 1: 정발행
                'TaxInvoiceType' => $this->mapInvoiceTypeToCode($taxInvoice->invoice_type),
                'ModifyCode' => '',
                'TaxType' => $taxType,
                'TaxCalcType' => 1, // 1: 소계합계
                'PurposeType' => 2, // 2: 청구
                'WriteDate' => $taxInvoice->issue_date->format('Ymd'),
                'AmountTotal' => (string) $supplyAmt,
                'TaxTotal' => (string) $taxAmt,
                'TotalAmount' => (string) $total,
                'Cash' => '0',
                'ChkBill' => '0',
                'Note' => '0',
                'Credit' => (string) $total,
                'Remark1' => $taxInvoice->description ?? '',
                'Remark2' => '',
                'Remark3' => '',
                'InvoicerParty' => [
                    'MgtNum' => $mgtNum,
                    'CorpNum' => $taxInvoice->supplier_corp_num,
                    'TaxRegID' => '',
                    'CorpName' => $taxInvoice->supplier_corp_name,
                    'CEOName' => $taxInvoice->supplier_ceo_name ?? '',
                    'Addr' => $taxInvoice->supplier_addr ?? '',
                    'BizType' => $taxInvoice->supplier_biz_type ?? '',
                    'BizClass' => $taxInvoice->supplier_biz_class ?? '',
                    'ContactID' => $setting->barobill_id,
                    'ContactName' => $setting->contact_name ?? '',
                    'TEL' => $setting->contact_tel ?? '',
                    'HP' => '',
                    'Email' => $setting->contact_id ?? '',
                ],
                'InvoiceeParty' => [
                    'MgtNum' => '',
                    'CorpNum' => str_replace('-', '', $taxInvoice->buyer_corp_num ?? ''),
                    'TaxRegID' => '',
                    'CorpName' => $taxInvoice->buyer_corp_name ?? '',
                    'CEOName' => $taxInvoice->buyer_ceo_name ?? '',
                    'Addr' => $taxInvoice->buyer_addr ?? '',
                    'BizType' => $taxInvoice->buyer_biz_type ?? '',
                    'BizClass' => $taxInvoice->buyer_biz_class ?? '',
                    'ContactID' => '',
                    'ContactName' => '',
                    'TEL' => '',
                    'HP' => '',
                    'Email' => $taxInvoice->buyer_contact_id ?? '',
                ],
                'BrokerParty' => [],
                'TaxInvoiceTradeLineItems' => [
                    'TaxInvoiceTradeLineItem' => $tradeLineItems,
                ],
            ],
            'SendSMS' => false,
            'ForceIssue' => false,
            'MailTitle' => '',
        ];
    }

    /**
     * 세금계산서 유형을 바로빌 코드로 매핑
     *
     * @return int 1: 세금계산서, 2: 계산서, 4: 수정세금계산서
     */
    private function mapInvoiceTypeToCode(string $type): int
    {
        return match ($type) {
            TaxInvoice::TYPE_TAX_INVOICE => 1,
            TaxInvoice::TYPE_INVOICE => 2,
            TaxInvoice::TYPE_MODIFIED_TAX_INVOICE => 4,
            default => 1,
        };
    }
}
