<?php

namespace App\Http\Controllers\Barobill;

use App\Http\Controllers\Controller;
use App\Models\Barobill\AccountCode;
use App\Models\Barobill\BarobillConfig;
use App\Models\Barobill\BarobillMember;
use App\Models\Barobill\CardTransaction;
use App\Models\Barobill\CardTransactionAmountLog;
use App\Models\Barobill\CardTransactionHide;
use App\Models\Barobill\CardTransactionSplit;
use App\Models\Finance\JournalEntry;
use App\Models\Finance\JournalEntryLine;
use App\Models\Tenants\Tenant;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * 바로빌 카드 사용내역 조회 컨트롤러
 */
class EcardController extends Controller
{
    /**
     * 바로빌 SOAP 설정
     */
    private ?string $certKey = null;

    private ?string $corpNum = null;

    private bool $isTestMode = false;

    private ?string $soapUrl = null;

    private ?\SoapClient $soapClient = null;

    // 바로빌 파트너사 (본사) 테넌트 ID
    private const HEADQUARTERS_TENANT_ID = 1;

    public function __construct()
    {
        // DB에서 활성화된 바로빌 설정 조회
        $activeConfig = BarobillConfig::where('is_active', true)->first();

        if ($activeConfig) {
            $this->certKey = $activeConfig->cert_key;
            $this->corpNum = $activeConfig->corp_num;
            $this->isTestMode = $activeConfig->environment === 'test';
            // 카드 조회는 CARD.asmx 사용
            $baseUrl = $this->isTestMode
                ? 'https://testws.baroservice.com'
                : 'https://ws.baroservice.com';
            $this->soapUrl = $baseUrl.'/CARD.asmx?WSDL';
        } else {
            $this->isTestMode = config('services.barobill.test_mode', true);
            $this->certKey = $this->isTestMode
                ? config('services.barobill.cert_key_test', '')
                : config('services.barobill.cert_key_prod', '');
            $this->corpNum = config('services.barobill.corp_num', '');
            $this->soapUrl = $this->isTestMode
                ? 'https://testws.baroservice.com/CARD.asmx?WSDL'
                : 'https://ws.baroservice.com/CARD.asmx?WSDL';
        }

        $this->initSoapClient();
    }

    /**
     * SOAP 클라이언트 초기화
     */
    private function initSoapClient(): void
    {
        if (! empty($this->certKey) || $this->isTestMode) {
            try {
                $context = stream_context_create([
                    'ssl' => [
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                        'allow_self_signed' => true,
                    ],
                ]);

                $this->soapClient = new \SoapClient($this->soapUrl, [
                    'trace' => true,
                    'encoding' => 'UTF-8',
                    'exceptions' => true,
                    'connection_timeout' => 30,
                    'stream_context' => $context,
                    'cache_wsdl' => WSDL_CACHE_NONE,
                ]);
            } catch (\Throwable $e) {
                Log::error('바로빌 카드 SOAP 클라이언트 생성 실패: '.$e->getMessage());
            }
        }
    }

    /**
     * 카드 사용내역 메인 페이지
     */
    public function index(Request $request): View|Response
    {
        if ($request->header('HX-Request')) {
            return response('', 200)->header('HX-Redirect', route('finance.card-transactions'));
        }

        // 현재 선택된 테넌트 정보
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $currentTenant = Tenant::find($tenantId);

        // 해당 테넌트의 바로빌 회원사 정보
        $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();

        // 테넌트별 서버 모드 적용 (회원사 설정 우선)
        $isTestMode = $barobillMember ? $barobillMember->isTestMode() : $this->isTestMode;

        // 서버 모드에 따라 SOAP 설정 재초기화
        if ($barobillMember && $barobillMember->server_mode) {
            $this->applyMemberServerMode($barobillMember);
        }

        return view('barobill.ecard.index', [
            'certKey' => $this->certKey,
            'corpNum' => $this->corpNum,
            'isTestMode' => $isTestMode,
            'hasSoapClient' => $this->soapClient !== null,
            'currentTenant' => $currentTenant,
            'barobillMember' => $barobillMember,
        ]);
    }

    /**
     * 회원사 서버 모드에 따라 SOAP 설정 적용
     */
    private function applyMemberServerMode(BarobillMember $member): void
    {
        $memberTestMode = $member->isTestMode();
        $targetEnv = $memberTestMode ? 'test' : 'production';

        // 해당 환경의 BarobillConfig 조회 (is_active 무관하게 환경에 맞는 설정 사용)
        $config = BarobillConfig::where('environment', $targetEnv)->first();

        if ($config) {
            $this->isTestMode = $memberTestMode;
            $this->certKey = $config->cert_key;
            $this->corpNum = $config->corp_num;
            $baseUrl = $config->base_url ?: ($memberTestMode
                ? 'https://testws.baroservice.com'
                : 'https://ws.baroservice.com');
            $this->soapUrl = $baseUrl.'/CARD.asmx?WSDL';

            // SOAP 클라이언트 재초기화
            $this->initSoapClient();

            Log::info('[Ecard] 서버 모드 적용', [
                'targetEnv' => $targetEnv,
                'certKey' => substr($this->certKey ?? '', 0, 10).'...',
                'corpNum' => $this->corpNum,
                'soapUrl' => $this->soapUrl,
            ]);
        } else {
            Log::warning('[Ecard] BarobillConfig 없음', ['targetEnv' => $targetEnv]);
        }
    }

    /**
     * 등록된 카드 목록 조회 (GetCardEx2)
     * 레퍼런스: https://dev.barobill.co.kr/docs/references/카드조회-API#GetCardEx2
     */
    public function cards(Request $request): JsonResponse
    {
        try {
            // 테넌트별 서버 모드 적용
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();
            if ($barobillMember && $barobillMember->server_mode) {
                $this->applyMemberServerMode($barobillMember);
            }

            $availOnly = $request->input('availOnly', 0);

            $result = $this->callSoap('GetCardEx2', [
                'AvailOnly' => (int) $availOnly,
            ]);

            if (! $result['success']) {
                return response()->json([
                    'success' => false,
                    'error' => $result['error'],
                    'error_code' => $result['error_code'] ?? null,
                ]);
            }

            $cards = [];
            $data = $result['data'];

            // GetCardEx2는 CardEx 배열을 반환
            $cardList = [];
            if (isset($data->CardEx)) {
                $cardList = is_array($data->CardEx) ? $data->CardEx : [$data->CardEx];
            }

            foreach ($cardList as $card) {
                if (! is_object($card)) {
                    continue;
                }

                $cardNum = $card->CardNum ?? '';
                // 에러 체크: CardNum이 음수면 에러 코드
                if (empty($cardNum) || (is_numeric($cardNum) && $cardNum < 0)) {
                    continue;
                }

                $cardCompanyCode = $card->CardCompanyCode ?? $card->CardCompany ?? '';
                $cardCompanyName = ! empty($card->CardCompanyName)
                    ? $card->CardCompanyName
                    : $this->getCardCompanyName($cardCompanyCode);

                $cards[] = [
                    'cardNum' => $cardNum,
                    'cardCompany' => $cardCompanyCode,
                    'cardBrand' => $cardCompanyName,
                    'alias' => $card->Alias ?? '',
                    'cardType' => $card->CardType ?? '',
                    'cardTypeName' => ($card->CardType ?? '') === '2' ? '법인카드' : '개인카드',
                    'status' => $card->Status ?? '',
                    'statusName' => $this->getCardStatusName($card->Status ?? ''),
                    'webId' => $card->WebId ?? '',
                ];
            }

            return response()->json([
                'success' => true,
                'cards' => $cards,
                'count' => count($cards),
            ]);
        } catch (\Throwable $e) {
            Log::error('카드 목록 조회 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '서버 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 카드 상태 코드 -> 이름 변환
     */
    private function getCardStatusName(string $status): string
    {
        $statuses = [
            '0' => '대기중',
            '1' => '정상',
            '2' => '해지',
            '3' => '수집오류',
            '4' => '일시중지',
        ];

        return $statuses[$status] ?? $status;
    }

    /**
     * 카드 사용내역 조회 (GetPeriodCardApprovalLog)
     */
    public function transactions(Request $request): JsonResponse
    {
        try {
            $startDate = $request->input('startDate', date('Ymd'));
            $endDate = $request->input('endDate', date('Ymd'));
            $cardNum = str_replace('-', '', $request->input('cardNum', ''));
            $page = (int) $request->input('page', 1);
            $limit = (int) $request->input('limit', 50);

            // 현재 테넌트의 바로빌 회원 정보 조회
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();
            $userId = $barobillMember?->barobill_id ?? '';

            // 테넌트별 서버 모드 적용
            if ($barobillMember && $barobillMember->server_mode) {
                $this->applyMemberServerMode($barobillMember);
            }

            // 디버그 로그
            Log::info('[ECard] 조회 요청', [
                'tenantId' => $tenantId,
                'userId' => $userId,
                'startDate' => $startDate,
                'endDate' => $endDate,
                'cardNum' => $cardNum,
                'page' => $page,
                'limit' => $limit,
            ]);

            // DB에서 저장된 계정과목 데이터 조회
            $savedData = CardTransaction::getByDateRange($tenantId, $startDate, $endDate, $cardNum ?: null);

            // DB에서 수동 입력 건 조회
            $manualTransactions = CardTransaction::where('tenant_id', $tenantId)
                ->where('is_manual', true)
                ->whereBetween('use_date', [$startDate, $endDate])
                ->when($cardNum, fn ($q) => $q->where('card_num', $cardNum))
                ->orderBy('use_date', 'desc')
                ->orderBy('use_time', 'desc')
                ->get();

            // 전체 카드 조회: 빈 값이면 모든 카드의 사용 내역 조회
            if (empty($cardNum)) {
                return $this->getAllCardsTransactions($userId, $startDate, $endDate, $page, $limit, $savedData, $manualTransactions);
            }

            // 단일 카드 조회 (전체 데이터를 가져와서 통계 계산 후 로컬 페이지네이션)
            $params = [
                'ID' => $userId,
                'CardNum' => $cardNum,
                'StartDate' => $startDate,
                'EndDate' => $endDate,
                'CountPerPage' => 10000,
                'CurrentPage' => 1,
                'OrderDirection' => 2, // 2:내림차순
            ];

            Log::info('[ECard] GetPeriodCardApprovalLog 호출', $params);

            $result = $this->callSoap('GetPeriodCardApprovalLog', $params);

            Log::info('[ECard] GetPeriodCardApprovalLog 응답', [
                'success' => $result['success'],
                'error' => $result['error'] ?? null,
                'error_code' => $result['error_code'] ?? null,
                'data_keys' => $result['success'] && isset($result['data']) ? (is_object($result['data']) ? get_object_vars($result['data']) : 'not_object') : null,
            ]);

            if (! $result['success']) {
                return response()->json([
                    'success' => false,
                    'error' => $result['error'],
                    'error_code' => $result['error_code'] ?? null,
                ]);
            }

            $resultData = $result['data'];

            // 에러 코드 체크
            $errorCode = $this->checkErrorCode($resultData);
            Log::info('[ECard] 에러 코드 체크', ['errorCode' => $errorCode]);

            if ($errorCode && ! in_array($errorCode, [-24005, -24001])) {
                return response()->json([
                    'success' => false,
                    'error' => $this->getErrorMessage($errorCode),
                    'error_code' => $errorCode,
                ]);
            }

            // 데이터가 없는 경우
            if ($errorCode && in_array($errorCode, [-24005, -24001])) {
                Log::info('[ECard] 데이터 없음 (에러코드로 판단)');
                // API 데이터 없어도 수동 건은 표시
                $manualLogs = $this->convertManualToLogs($manualTransactions);

                return response()->json([
                    'success' => true,
                    'data' => [
                        'logs' => $manualLogs['logs'],
                        'summary' => $manualLogs['summary'],
                        'pagination' => ['currentPage' => 1, 'maxPageNum' => 1],
                    ],
                ]);
            }

            // 데이터 파싱 (저장된 계정과목 병합)
            $logs = $this->parseTransactionLogs($resultData, $savedData);

            // 숨김 키 필터링
            $hiddenKeys = CardTransactionHide::getHiddenKeys($tenantId, $startDate, $endDate);
            if ($hiddenKeys->isNotEmpty()) {
                $hiddenSet = $hiddenKeys->flip();
                $logs = $this->filterHiddenLogs($logs, $hiddenSet);
            }

            // 수동 입력 건 병합
            $manualLogs = $this->convertManualToLogs($manualTransactions);
            $allLogs = array_merge($logs['logs'], $manualLogs['logs']);

            // 날짜/시간 기준 정렬 (최신순)
            usort($allLogs, fn ($a, $b) => strcmp($b['useDt'] ?? '', $a['useDt'] ?? ''));

            // 전체 데이터에서 통계 계산 (공제/불공제 포함)
            $mergedSummary = $this->mergeSummaries($logs['summary'], $manualLogs['summary']);

            // 로컬 페이지네이션
            $totalCount = count($allLogs);
            $maxPageNum = (int) ceil($totalCount / $limit);
            $startIndex = ($page - 1) * $limit;
            $paginatedLogs = array_slice($allLogs, $startIndex, $limit);

            Log::info('[ECard] 파싱 결과', [
                'total_count' => $totalCount,
                'page_count' => count($paginatedLogs),
                'manual_count' => count($manualLogs['logs']),
                'summary' => $mergedSummary,
            ]);

            return response()->json([
                'success' => true,
                'data' => [
                    'logs' => $paginatedLogs,
                    'pagination' => [
                        'currentPage' => $page,
                        'countPerPage' => $limit,
                        'maxPageNum' => $maxPageNum,
                        'maxIndex' => $totalCount,
                    ],
                    'summary' => $mergedSummary,
                ],
            ]);
        } catch (\Throwable $e) {
            Log::error('카드 사용내역 조회 오류: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'success' => false,
                'error' => '서버 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 전체 카드의 사용 내역 조회
     */
    private function getAllCardsTransactions(string $userId, string $startDate, string $endDate, int $page, int $limit, $savedData = null, $manualTransactions = null): JsonResponse
    {
        Log::info('[ECard] 전체 카드 조회 시작', [
            'userId' => $userId,
            'startDate' => $startDate,
            'endDate' => $endDate,
        ]);

        // 먼저 카드 목록 조회 (GetCardEx2 사용)
        $cardResult = $this->callSoap('GetCardEx2', ['AvailOnly' => 0]);

        Log::info('[ECard] GetCardEx2 응답', [
            'success' => $cardResult['success'],
            'error' => $cardResult['error'] ?? null,
        ]);

        if (! $cardResult['success']) {
            return response()->json([
                'success' => false,
                'error' => $cardResult['error'],
            ]);
        }

        $cardList = [];
        $data = $cardResult['data'];
        if (isset($data->CardEx)) {
            $cardList = is_array($data->CardEx) ? $data->CardEx : [$data->CardEx];
        }

        Log::info('[ECard] 카드 목록', [
            'count' => count($cardList),
            'cards' => array_map(fn ($c) => [
                'CardNum' => $c->CardNum ?? 'N/A',
                'Alias' => $c->Alias ?? 'N/A',
                'Status' => $c->Status ?? 'N/A',
            ], $cardList),
        ]);

        // 숨김 키 조회
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $hiddenKeys = CardTransactionHide::getHiddenKeys($tenantId, $startDate, $endDate);
        $hiddenSet = $hiddenKeys->isNotEmpty() ? $hiddenKeys->flip() : collect();

        $allLogs = [];
        $totalAmount = 0;
        $approvalCount = 0;
        $cancelCount = 0;
        $totalTax = 0;

        foreach ($cardList as $card) {
            if (! is_object($card)) {
                continue;
            }

            $cardNum = $card->CardNum ?? '';
            if (empty($cardNum) || (is_numeric($cardNum) && $cardNum < 0)) {
                continue;
            }

            $params = [
                'ID' => $userId,
                'CardNum' => $cardNum,
                'StartDate' => $startDate,
                'EndDate' => $endDate,
                'CountPerPage' => 1000,
                'CurrentPage' => 1,
                'OrderDirection' => 2,
            ];

            Log::info('[ECard] 카드별 사용내역 조회', ['cardNum' => $cardNum, 'params' => $params]);

            $cardResult = $this->callSoap('GetPeriodCardApprovalLog', $params);

            Log::info('[ECard] 카드별 응답', [
                'cardNum' => $cardNum,
                'success' => $cardResult['success'],
                'error' => $cardResult['error'] ?? null,
                'data_type' => isset($cardResult['data']) ? gettype($cardResult['data']) : 'null',
                'data_props' => isset($cardResult['data']) && is_object($cardResult['data'])
                    ? array_keys(get_object_vars($cardResult['data']))
                    : null,
            ]);

            if ($cardResult['success']) {
                $cardData = $cardResult['data'];
                $errorCode = $this->checkErrorCode($cardData);

                Log::info('[ECard] 카드별 에러코드', [
                    'cardNum' => $cardNum,
                    'errorCode' => $errorCode,
                    'CurrentPage' => $cardData->CurrentPage ?? 'N/A',
                    'MaxIndex' => $cardData->MaxIndex ?? 'N/A',
                    'hasCardLogList' => isset($cardData->CardLogList),
                    'hasCardApprovalLog' => isset($cardData->CardLogList) && isset($cardData->CardLogList->CardApprovalLog),
                ]);

                if (! $errorCode || in_array($errorCode, [-24005, -24001])) {
                    $parsed = $this->parseTransactionLogs($cardData, $savedData);

                    // 숨김 키 필터링
                    if ($hiddenSet->isNotEmpty()) {
                        $parsed = $this->filterHiddenLogs($parsed, $hiddenSet);
                    }

                    Log::info('[ECard] 카드별 파싱 결과', [
                        'cardNum' => $cardNum,
                        'logs_count' => count($parsed['logs']),
                    ]);
                    foreach ($parsed['logs'] as $log) {
                        $log['cardBrand'] = $this->getCardCompanyName($card->CardCompany ?? '');
                        $allLogs[] = $log;
                    }
                    $totalAmount += $parsed['summary']['totalAmount'];
                    $approvalCount += $parsed['summary']['approvalCount'];
                    $cancelCount += $parsed['summary']['cancelCount'];
                    $totalTax += $parsed['summary']['totalTax'] ?? 0;
                }
            }
        }

        // 수동 입력 건 병합
        if ($manualTransactions && $manualTransactions->isNotEmpty()) {
            $manualLogs = $this->convertManualToLogs($manualTransactions);
            foreach ($manualLogs['logs'] as $mLog) {
                $allLogs[] = $mLog;
            }
            $totalAmount += $manualLogs['summary']['totalAmount'];
            $approvalCount += $manualLogs['summary']['approvalCount'];
            $cancelCount += $manualLogs['summary']['cancelCount'];
        }

        // 날짜/시간 기준 정렬 (최신순)
        usort($allLogs, function ($a, $b) {
            return strcmp($b['useDt'] ?? '', $a['useDt'] ?? '');
        });

        // 전체 데이터에서 공제/불공제 통계 계산 (불공제 부가세는 합계 제외)
        $deductibleAmount = 0;
        $deductibleCount = 0;
        $nonDeductibleAmount = 0;
        $nonDeductibleCount = 0;
        $totalTax = 0;

        foreach ($allLogs as $log) {
            $type = $log['deductionType'] ?? 'non_deductible';
            $amount = abs($log['approvalAmount'] ?? 0);
            if ($type === 'deductible') {
                $deductibleAmount += $amount;
                $deductibleCount++;
                $totalTax += abs($log['effectiveTax'] ?? $log['tax'] ?? 0);
            } else {
                $nonDeductibleAmount += $amount;
                $nonDeductibleCount++;
            }
        }

        // 페이지네이션
        $totalCount = count($allLogs);
        $maxPageNum = (int) ceil($totalCount / $limit);
        $startIndex = ($page - 1) * $limit;
        $paginatedLogs = array_slice($allLogs, $startIndex, $limit);

        return response()->json([
            'success' => true,
            'data' => [
                'logs' => $paginatedLogs,
                'pagination' => [
                    'currentPage' => $page,
                    'countPerPage' => $limit,
                    'maxPageNum' => $maxPageNum,
                    'maxIndex' => $totalCount,
                ],
                'summary' => [
                    'totalAmount' => $totalAmount,
                    'count' => $totalCount,
                    'approvalCount' => $approvalCount,
                    'cancelCount' => $cancelCount,
                    'totalTax' => $totalTax,
                    'deductibleAmount' => $deductibleAmount,
                    'deductibleCount' => $deductibleCount,
                    'nonDeductibleAmount' => $nonDeductibleAmount,
                    'nonDeductibleCount' => $nonDeductibleCount,
                ],
            ],
        ]);
    }

    /**
     * 거래 내역 파싱 (저장된 계정과목 병합)
     */
    private function parseTransactionLogs($resultData, $savedData = null): array
    {
        $logs = [];
        $totalAmount = 0;
        $approvalCount = 0;
        $cancelCount = 0;
        $totalTax = 0;
        $deductibleAmount = 0;
        $deductibleCount = 0;
        $nonDeductibleAmount = 0;
        $nonDeductibleCount = 0;

        $rawLogs = [];
        if (isset($resultData->CardLogList) && isset($resultData->CardLogList->CardApprovalLog)) {
            $rawLogs = is_array($resultData->CardLogList->CardApprovalLog)
                ? $resultData->CardLogList->CardApprovalLog
                : [$resultData->CardLogList->CardApprovalLog];
        }

        foreach ($rawLogs as $log) {
            $amount = floatval($log->ApprovalAmount ?? 0);
            $rawApprovalType = $log->ApprovalType ?? null;
            $approvalType = (string) ($rawApprovalType ?? '1');

            // ApprovalType: 1=승인 or "승인", 2=취소 or "취소"
            // API에서 한글 텍스트로 반환될 수 있으므로 취소가 아닌 경우 모두 승인으로 처리
            $isApproval = ! in_array($approvalType, ['2', '취소'], true);

            // 디버깅: ApprovalType 값 확인 (첫 번째 로그만)
            if (count($logs) === 0) {
                Log::info('[ECard] 첫 번째 로그 전체 데이터', [
                    'log_object' => json_encode($log),
                    'ApprovalType_raw' => $rawApprovalType,
                    'ApprovalType_type' => gettype($rawApprovalType),
                    'ApprovalType_casted' => $approvalType,
                    'isApproval' => $isApproval,
                    'amount' => $amount,
                ]);
            }

            if ($isApproval) {
                $totalAmount += $amount;
                $approvalCount++;
            } else {
                $cancelCount++;
            }

            // 사용일시 파싱
            $useDT = $log->UseDT ?? '';
            $useDate = '';
            $useTime = '';
            $dateTime = '';

            if (! empty($useDT) && strlen($useDT) >= 8) {
                $useDate = substr($useDT, 0, 8);
                if (strlen($useDT) >= 14) {
                    $useTime = substr($useDT, 8, 6);
                    $dateTime = substr($useDT, 0, 4).'-'.substr($useDT, 4, 2).'-'.substr($useDT, 6, 2).' '.
                        substr($useDT, 8, 2).':'.substr($useDT, 10, 2).':'.substr($useDT, 12, 2);
                } else {
                    $dateTime = substr($useDT, 0, 4).'-'.substr($useDT, 4, 2).'-'.substr($useDT, 6, 2);
                }
            }

            $cardNum = $log->CardNum ?? '';
            $approvalNum = $log->ApprovalNum ?? '';

            // 고유 키 생성하여 저장된 데이터와 매칭
            $uniqueKey = implode('|', [$cardNum, $useDT, $approvalNum, (int) $amount]);
            $savedItem = $savedData?->get($uniqueKey);

            $logItem = [
                'cardNum' => $cardNum,
                'cardNumMasked' => $this->maskCardNumber($cardNum),
                'cardCompany' => $log->CardCompany ?? '',
                'cardBrand' => $this->getCardCompanyName($log->CardCompany ?? ''),
                'useDt' => $useDT,
                'useDate' => $useDate,
                'useTime' => $useTime,
                'useDateTime' => $dateTime,
                'approvalNum' => $approvalNum,
                'approvalType' => $isApproval ? '1' : '2',
                'approvalTypeName' => $isApproval ? '승인' : '취소',
                'approvalAmount' => $amount,
                'approvalAmountFormatted' => number_format($amount),
                'tax' => floatval($log->Tax ?? 0),
                'taxFormatted' => number_format(floatval($log->Tax ?? 0)),
                'serviceCharge' => floatval($log->ServiceCharge ?? 0),
                'paymentPlan' => $log->PaymentPlan ?? '0',
                'paymentPlanName' => $this->getPaymentPlanName($log->PaymentPlan ?? '0'),
                'currencyCode' => $log->CurrencyCode ?? 'KRW',
                'merchantName' => $log->UseStoreName ?? '',
                'merchantBizNum' => $log->UseStoreCorpNum ?? '',
                'merchantAddr' => $log->UseStoreAddr ?? '',
                'merchantCeo' => $log->UseStoreCeo ?? '',
                'merchantBizType' => $log->UseStoreBizType ?? '',
                'merchantTel' => $log->UseStoreTel ?? '',
                'memo' => $log->Memo ?? '',
                'useKey' => $log->UseKey ?? '',
                // 저장된 계정과목 정보 병합
                'accountCode' => $savedItem?->account_code ?? '',
                'accountName' => $savedItem?->account_name ?? '',
                // 수정 가능한 필드들 (저장된 값 또는 기본값)
                'deductionType' => $savedItem?->deduction_type ?? ($log->UseStoreCorpNum ? 'deductible' : 'non_deductible'),
                'evidenceName' => $savedItem?->evidence_name ?? ($log->UseStoreName ?? ''),
                'description' => $savedItem?->description ?? ($log->UseStoreBizType ?? $log->Memo ?? ''),
                'isSaved' => $savedItem !== null,
                // 금액 수정 관련 필드
                'modifiedSupplyAmount' => $savedItem?->modified_supply_amount !== null ? (float) $savedItem->modified_supply_amount : null,
                'modifiedTax' => $savedItem?->modified_tax !== null ? (float) $savedItem->modified_tax : null,
                'effectiveSupplyAmount' => $savedItem?->modified_supply_amount !== null
                    ? (float) $savedItem->modified_supply_amount
                    : ($amount - floatval($log->Tax ?? 0)),
                'effectiveTax' => $savedItem?->modified_tax !== null
                    ? (float) $savedItem->modified_tax
                    : floatval($log->Tax ?? 0),
                'isAmountModified' => $savedItem?->modified_supply_amount !== null || $savedItem?->modified_tax !== null,
                'isManual' => (bool) ($savedItem?->is_manual ?? false),
                'dbId' => $savedItem?->id ?? null,
            ];

            // 공제/불공제 통계 집계
            $deductionType = $logItem['deductionType'];
            $absAmount = abs($amount);
            if ($deductionType === 'deductible') {
                $deductibleAmount += $absAmount;
                $deductibleCount++;
                $totalTax += abs($logItem['effectiveTax']);
            } else {
                $nonDeductibleAmount += $absAmount;
                $nonDeductibleCount++;
            }

            $logs[] = $logItem;
        }

        return [
            'logs' => $logs,
            'summary' => [
                'totalAmount' => $totalAmount,
                'count' => count($logs),
                'approvalCount' => $approvalCount,
                'cancelCount' => $cancelCount,
                'totalTax' => $totalTax,
                'deductibleAmount' => $deductibleAmount,
                'deductibleCount' => $deductibleCount,
                'nonDeductibleAmount' => $nonDeductibleAmount,
                'nonDeductibleCount' => $nonDeductibleCount,
            ],
        ];
    }

    /**
     * 에러 코드 체크
     */
    private function checkErrorCode($data): ?int
    {
        if (isset($data->CurrentPage) && is_numeric($data->CurrentPage) && $data->CurrentPage < 0) {
            return (int) $data->CurrentPage;
        }
        if (isset($data->CardNum) && is_numeric($data->CardNum) && $data->CardNum < 0) {
            return (int) $data->CardNum;
        }

        return null;
    }

    /**
     * 에러 메시지 반환
     */
    private function getErrorMessage(int $errorCode): string
    {
        $messages = [
            -10002 => '인증 실패 (-10002). CERTKEY가 올바르지 않거나 만료되었습니다.',
            -24005 => '사용자 정보 불일치 (-24005). 사업자번호를 확인해주세요.',
            -24001 => '등록된 카드가 없습니다 (-24001).',
            -24005 => '조회된 데이터가 없습니다 (-24005).',
            -25006 => '카드번호가 잘못되었습니다 (-25006).',
            -25007 => '조회 기간이 잘못되었습니다 (-25007).',
        ];

        return $messages[$errorCode] ?? '바로빌 API 오류: '.$errorCode;
    }

    /**
     * 카드번호 마스킹
     */
    private function maskCardNumber(string $cardNum): string
    {
        if (strlen($cardNum) < 8) {
            return $cardNum;
        }

        return substr($cardNum, 0, 4).'-****-****-'.substr($cardNum, -4);
    }

    /**
     * 카드사 코드 -> 카드사명 변환
     */
    private function getCardCompanyName(string $code): string
    {
        $companies = [
            '01' => '비씨',
            '02' => 'KB국민',
            '03' => '하나(외환)',
            '04' => '삼성',
            '06' => '신한',
            '07' => '현대',
            '08' => '롯데',
            '11' => 'NH농협',
            '12' => '수협',
            '13' => '씨티',
            '14' => '우리',
            '15' => '광주',
            '16' => '전북',
            '21' => '하나',
            '22' => '제주',
            '23' => 'SC제일',
            '25' => 'KDB산업',
            '26' => 'IBK기업',
            '27' => '새마을금고',
            '28' => '신협',
            '29' => '저축은행',
            '30' => '우체국',
            '31' => '카카오뱅크',
            '32' => 'K뱅크',
            '33' => '토스뱅크',
        ];

        return $companies[$code] ?? $code;
    }

    /**
     * 할부 이름 반환
     */
    private function getPaymentPlanName(string $plan): string
    {
        if (empty($plan) || $plan === '0' || $plan === '00') {
            return '일시불';
        }

        return $plan.'개월';
    }

    /**
     * 계정과목 목록 조회
     */
    public function accountCodes(): JsonResponse
    {
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $codes = AccountCode::getActiveByTenant($tenantId);

        return response()->json([
            'success' => true,
            'data' => $codes->map(fn ($c) => [
                'id' => $c->id,
                'code' => $c->code,
                'name' => $c->name,
                'category' => $c->category,
            ]),
        ]);
    }

    /**
     * 카드 사용내역 저장 (계정과목 포함)
     */
    public function save(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $transactions = $request->input('transactions', []);

            if (empty($transactions)) {
                return response()->json([
                    'success' => false,
                    'error' => '저장할 데이터가 없습니다.',
                ]);
            }

            $saved = 0;
            $updated = 0;

            DB::beginTransaction();

            foreach ($transactions as $trans) {
                $data = [
                    'tenant_id' => $tenantId,
                    'card_num' => $trans['cardNum'] ?? '',
                    'card_company' => $trans['cardCompany'] ?? '',
                    'card_company_name' => $trans['cardBrand'] ?? '',
                    'use_dt' => $trans['useDt'] ?? '',
                    'use_date' => $trans['useDate'] ?? '',
                    'use_time' => $trans['useTime'] ?? '',
                    'approval_num' => $trans['approvalNum'] ?? '',
                    'approval_type' => $trans['approvalType'] ?? '',
                    'approval_amount' => floatval($trans['approvalAmount'] ?? 0),
                    'tax' => floatval($trans['tax'] ?? 0),
                    'service_charge' => floatval($trans['serviceCharge'] ?? 0),
                    'payment_plan' => $trans['paymentPlan'] ?? '',
                    'currency_code' => $trans['currencyCode'] ?? 'KRW',
                    'merchant_name' => $trans['merchantName'] ?? '',
                    'merchant_biz_num' => $trans['merchantBizNum'] ?? '',
                    'merchant_addr' => $trans['merchantAddr'] ?? '',
                    'merchant_ceo' => $trans['merchantCeo'] ?? '',
                    'merchant_biz_type' => $trans['merchantBizType'] ?? '',
                    'merchant_tel' => $trans['merchantTel'] ?? '',
                    'memo' => $trans['memo'] ?? '',
                    'use_key' => $trans['useKey'] ?? '',
                    'account_code' => $trans['accountCode'] ?? null,
                    'account_name' => $trans['accountName'] ?? null,
                    'deduction_type' => $trans['deductionType'] ?? null,
                    'evidence_name' => $trans['evidenceName'] ?? null,
                    'description' => $trans['description'] ?? null,
                ];

                // Upsert: 있으면 업데이트, 없으면 생성
                $existing = CardTransaction::where('tenant_id', $tenantId)
                    ->where('card_num', $data['card_num'])
                    ->where('use_dt', $data['use_dt'])
                    ->where('approval_num', $data['approval_num'])
                    ->where('approval_amount', $data['approval_amount'])
                    ->first();

                // 수정 금액 처리
                $modifiedSupplyAmount = isset($trans['modifiedSupplyAmount']) && $trans['modifiedSupplyAmount'] !== null
                    ? floatval($trans['modifiedSupplyAmount']) : null;
                $modifiedTax = isset($trans['modifiedTax']) && $trans['modifiedTax'] !== null
                    ? floatval($trans['modifiedTax']) : null;

                $data['modified_supply_amount'] = $modifiedSupplyAmount;
                $data['modified_tax'] = $modifiedTax;

                if ($existing) {
                    // 금액 변경 감지 및 이력 기록
                    $oldSupply = $existing->modified_supply_amount !== null
                        ? (float) $existing->modified_supply_amount
                        : ((float) $existing->approval_amount - (float) $existing->tax);
                    $oldTax = $existing->modified_tax !== null
                        ? (float) $existing->modified_tax
                        : (float) $existing->tax;

                    $newSupply = $modifiedSupplyAmount !== null
                        ? $modifiedSupplyAmount
                        : ((float) $existing->approval_amount - (float) $existing->tax);
                    $newTax = $modifiedTax !== null
                        ? $modifiedTax
                        : (float) $existing->tax;

                    $amountChanged = abs($oldSupply - $newSupply) > 0.01 || abs($oldTax - $newTax) > 0.01;

                    if ($amountChanged) {
                        $uniqueKey = implode('|', [
                            $existing->card_num,
                            $existing->use_dt,
                            $existing->approval_num,
                            (int) $existing->approval_amount,
                        ]);

                        CardTransactionAmountLog::create([
                            'card_transaction_id' => $existing->id,
                            'original_unique_key' => $uniqueKey,
                            'before_supply_amount' => $oldSupply,
                            'before_tax' => $oldTax,
                            'after_supply_amount' => $newSupply,
                            'after_tax' => $newTax,
                            'modified_by' => auth()->id(),
                            'modified_by_name' => auth()->user()?->name ?? '',
                            'ip_address' => $request->ip(),
                        ]);
                    }

                    // 계정과목 및 수정 가능한 필드들 업데이트
                    $existing->update([
                        'account_code' => $data['account_code'],
                        'account_name' => $data['account_name'],
                        'deduction_type' => $data['deduction_type'],
                        'evidence_name' => $data['evidence_name'],
                        'description' => $data['description'],
                        'modified_supply_amount' => $data['modified_supply_amount'],
                        'modified_tax' => $data['modified_tax'],
                    ]);
                    $updated++;
                } else {
                    CardTransaction::create($data);
                    $saved++;
                }
            }

            DB::commit();

            return response()->json([
                'success' => true,
                'message' => "저장 완료: 신규 {$saved}건, 수정 {$updated}건",
                'saved' => $saved,
                'updated' => $updated,
            ]);
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('카드 사용내역 저장 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '저장 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 엑셀 다운로드
     */
    public function exportExcel(Request $request): StreamedResponse|JsonResponse
    {
        try {
            $startDate = $request->input('startDate', date('Ymd'));
            $endDate = $request->input('endDate', date('Ymd'));
            $logs = $request->input('logs', []);
            $splitsData = $request->input('splits', []);

            // 데이터가 없으면 안내
            if (empty($logs)) {
                return response()->json([
                    'success' => false,
                    'error' => '내보낼 데이터가 없습니다.',
                ]);
            }

            $filename = "카드사용내역_{$startDate}_{$endDate}.csv";

            return response()->streamDownload(function () use ($logs, $splitsData) {
                $handle = fopen('php://output', 'w');

                // UTF-8 BOM for Excel
                fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));

                // 헤더
                fputcsv($handle, [
                    '구분',
                    '사용일시',
                    '카드번호',
                    '카드사',
                    '공제여부',
                    '사업자번호',
                    '가맹점명',
                    '증빙/판매자상호',
                    '내역',
                    '합계금액',
                    '공급가액',
                    '세액',
                    '승인번호',
                    '계정과목코드',
                    '계정과목명',
                    '메모',
                ]);

                // 데이터
                foreach ($logs as $log) {
                    $dateTime = $log['useDateTime'] ?? '';
                    $cardNum = $log['cardNum'] ?? '';
                    $cardBrand = $log['cardBrand'] ?? '';
                    $approvalNum = $log['approvalNum'] ?? '';
                    $approvalAmount = floatval($log['approvalAmount'] ?? 0);
                    $tax = floatval($log['effectiveTax'] ?? $log['tax'] ?? 0);
                    $supplyAmount = floatval($log['effectiveSupplyAmount'] ?? ($approvalAmount - $tax));
                    $totalAmount = $supplyAmount + $tax;

                    // 카드번호를 문자형으로 강제 (엑셀 과학적 표기 방지)
                    $cardNumText = $cardNum ? "=\"{$cardNum}\"" : '';

                    // 고유키로 분리 데이터 확인
                    $uniqueKey = $log['uniqueKey'] ?? implode('|', [
                        $cardNum,
                        $log['useDt'] ?? '',
                        $approvalNum,
                        (int) $approvalAmount,
                    ]);

                    $splits = $splitsData[$uniqueKey] ?? [];
                    $hasSplits = count($splits) > 0;

                    // 공제여부
                    $deductionType = $log['deductionType'] ?? ($log['merchantBizNum'] ? 'deductible' : 'non_deductible');
                    $deductionText = ($deductionType === 'non_deductible') ? '불공' : '공제';

                    // 사업자번호, 가맹점명
                    $merchantBizNum = $log['merchantBizNum'] ?? '';
                    $merchantName = $log['merchantName'] ?? '';

                    // 증빙/판매자상호, 내역
                    $evidenceName = $log['evidenceName'] ?? $merchantName;
                    $description = $log['description'] ?? $log['merchantBizType'] ?? '';

                    if ($hasSplits) {
                        // 분리가 있는 경우: 원본 행 (합계 표시)
                        fputcsv($handle, [
                            '원본',
                            $dateTime,
                            $cardNumText,
                            $cardBrand,
                            '-',
                            $merchantBizNum,
                            $merchantName,
                            $evidenceName,
                            $description,
                            number_format($totalAmount),
                            number_format($supplyAmount),
                            number_format($tax),
                            $approvalNum,
                            '-',
                            '분리됨 ('.count($splits).'건)',
                            '',
                        ]);

                        // 각 분리 행 출력
                        foreach ($splits as $index => $split) {
                            $splitDeductionType = $split['deduction_type'] ?? $split['deductionType'] ?? 'deductible';
                            $splitDeductionText = ($splitDeductionType === 'non_deductible') ? '불공' : '공제';
                            $splitSupplyAmount = floatval($split['split_supply_amount'] ?? $split['supplyAmount'] ?? 0);
                            $splitTax = floatval($split['split_tax'] ?? $split['tax'] ?? 0);
                            $splitTotal = $splitSupplyAmount + $splitTax;
                            $splitEvidenceName = $split['evidence_name'] ?? $split['evidenceName'] ?? '';
                            $splitDescription = $split['description'] ?? '';
                            $splitAccountCode = $split['account_code'] ?? $split['accountCode'] ?? '';
                            $splitAccountName = $split['account_name'] ?? $split['accountName'] ?? '';
                            $splitMemo = $split['memo'] ?? '';

                            fputcsv($handle, [
                                '└ 분리 #'.($index + 1),
                                '',
                                '',
                                '',
                                $splitDeductionText,
                                '',
                                '',
                                $splitEvidenceName,
                                $splitDescription,
                                number_format($splitTotal),
                                number_format($splitSupplyAmount),
                                number_format($splitTax),
                                '',
                                $splitAccountCode,
                                $splitAccountName,
                                $splitMemo,
                            ]);
                        }
                    } else {
                        // 분리가 없는 경우: 일반 행
                        fputcsv($handle, [
                            '일반',
                            $dateTime,
                            $cardNumText,
                            $cardBrand,
                            $deductionText,
                            $merchantBizNum,
                            $merchantName,
                            $evidenceName,
                            $description,
                            number_format($totalAmount),
                            number_format($supplyAmount),
                            number_format($tax),
                            $approvalNum,
                            $log['accountCode'] ?? '',
                            $log['accountName'] ?? '',
                            '',
                        ]);
                    }
                }

                fclose($handle);
            }, $filename, [
                'Content-Type' => 'text/csv; charset=utf-8',
                'Content-Disposition' => 'attachment; filename="'.$filename.'"',
            ]);
        } catch (\Throwable $e) {
            Log::error('엑셀 다운로드 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '다운로드 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 분리 내역 조회
     */
    public function splits(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $startDate = $request->input('startDate', date('Ymd'));
            $endDate = $request->input('endDate', date('Ymd'));

            $splits = CardTransactionSplit::getByDateRange($tenantId, $startDate, $endDate);

            return response()->json([
                'success' => true,
                'data' => $splits,
            ]);
        } catch (\Throwable $e) {
            Log::error('분리 내역 조회 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '조회 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 분리 저장
     */
    public function saveSplits(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $uniqueKey = $request->input('uniqueKey');
            $originalData = $request->input('originalData', []);
            $splits = $request->input('splits', []);

            if (empty($uniqueKey)) {
                return response()->json([
                    'success' => false,
                    'error' => '고유키가 없습니다.',
                ]);
            }

            // 분리 금액 합계 검증 (공급가액 + 부가세 합계)
            $originalAmount = floatval($originalData['originalAmount'] ?? 0);
            $splitTotal = array_sum(array_map(function ($s) {
                if (isset($s['supplyAmount']) && isset($s['tax'])) {
                    return floatval($s['supplyAmount']) + floatval($s['tax']);
                }

                return floatval($s['amount'] ?? 0);
            }, $splits));

            if (abs($originalAmount - $splitTotal) > 0.01) {
                return response()->json([
                    'success' => false,
                    'error' => "분리 금액 합계({$splitTotal})가 원본 금액({$originalAmount})과 일치하지 않습니다.",
                ]);
            }

            DB::beginTransaction();

            CardTransactionSplit::saveSplits($tenantId, $uniqueKey, $originalData, $splits);

            DB::commit();

            return response()->json([
                'success' => true,
                'message' => '분리가 저장되었습니다.',
                'splitCount' => count($splits),
            ]);
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('분리 저장 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '저장 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 분리 삭제 (원본으로 복원)
     */
    public function deleteSplits(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $uniqueKey = $request->input('uniqueKey');

            if (empty($uniqueKey)) {
                return response()->json([
                    'success' => false,
                    'error' => '고유키가 없습니다.',
                ]);
            }

            $deleted = CardTransactionSplit::deleteSplits($tenantId, $uniqueKey);

            return response()->json([
                'success' => true,
                'message' => '분리가 삭제되었습니다.',
                'deleted' => $deleted,
            ]);
        } catch (\Throwable $e) {
            Log::error('분리 삭제 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '삭제 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 수동 거래 등록
     */
    public function storeManual(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $validated = $request->validate([
                'card_num' => 'required|string',
                'card_company' => 'required|string',
                'use_date' => 'required|date_format:Ymd',
                'use_time' => 'nullable|string|max:6',
                'approval_num' => 'nullable|string',
                'approval_type' => 'required|in:1,2',
                'approval_amount' => 'required|numeric',
                'tax' => 'required|numeric',
                'merchant_name' => 'nullable|string',
                'merchant_biz_num' => 'nullable|string',
                'deduction_type' => 'required|in:deductible,non_deductible',
                'account_code' => 'nullable|string',
                'account_name' => 'nullable|string',
                'evidence_name' => 'nullable|string',
                'description' => 'nullable|string',
                'memo' => 'nullable|string',
            ]);

            $useTime = $validated['use_time'] ?? '000000';
            $useDt = $validated['use_date'].$useTime;

            $transaction = CardTransaction::create([
                'tenant_id' => $tenantId,
                'card_num' => $validated['card_num'],
                'card_company' => $validated['card_company'],
                'card_company_name' => $this->getCardCompanyName($validated['card_company']),
                'use_dt' => $useDt,
                'use_date' => $validated['use_date'],
                'use_time' => $useTime,
                'approval_num' => $validated['approval_num'] ?? '',
                'approval_type' => $validated['approval_type'],
                'approval_amount' => $validated['approval_amount'],
                'tax' => $validated['tax'],
                'service_charge' => 0,
                'payment_plan' => '0',
                'currency_code' => 'KRW',
                'merchant_name' => $validated['merchant_name'] ?? '',
                'merchant_biz_num' => $validated['merchant_biz_num'] ?? '',
                'deduction_type' => $validated['deduction_type'],
                'account_code' => $validated['account_code'] ?? null,
                'account_name' => $validated['account_name'] ?? null,
                'evidence_name' => $validated['evidence_name'] ?? '',
                'description' => $validated['description'] ?? '',
                'memo' => $validated['memo'] ?? '',
                'use_key' => '',
                'is_manual' => true,
            ]);

            return response()->json([
                'success' => true,
                'message' => '수동 거래가 등록되었습니다.',
                'data' => ['id' => $transaction->id],
            ]);
        } catch (\Illuminate\Validation\ValidationException $e) {
            return response()->json([
                'success' => false,
                'error' => '입력 데이터가 올바르지 않습니다.',
                'errors' => $e->errors(),
            ], 422);
        } catch (\Throwable $e) {
            Log::error('수동 거래 등록 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '등록 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 수동 거래 수정
     */
    public function updateManual(Request $request, int $id): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $transaction = CardTransaction::where('id', $id)
                ->where('tenant_id', $tenantId)
                ->where('is_manual', true)
                ->firstOrFail();

            $validated = $request->validate([
                'card_num' => 'required|string',
                'card_company' => 'required|string',
                'use_date' => 'required|date_format:Ymd',
                'use_time' => 'nullable|string|max:6',
                'approval_num' => 'nullable|string',
                'approval_type' => 'required|in:1,2',
                'approval_amount' => 'required|numeric',
                'tax' => 'required|numeric',
                'merchant_name' => 'nullable|string',
                'merchant_biz_num' => 'nullable|string',
                'deduction_type' => 'required|in:deductible,non_deductible',
                'account_code' => 'nullable|string',
                'account_name' => 'nullable|string',
                'evidence_name' => 'nullable|string',
                'description' => 'nullable|string',
                'memo' => 'nullable|string',
            ]);

            $useTime = $validated['use_time'] ?? '000000';
            $useDt = $validated['use_date'].$useTime;

            $transaction->update([
                'card_num' => $validated['card_num'],
                'card_company' => $validated['card_company'],
                'card_company_name' => $this->getCardCompanyName($validated['card_company']),
                'use_dt' => $useDt,
                'use_date' => $validated['use_date'],
                'use_time' => $useTime,
                'approval_num' => $validated['approval_num'] ?? '',
                'approval_type' => $validated['approval_type'],
                'approval_amount' => $validated['approval_amount'],
                'tax' => $validated['tax'],
                'merchant_name' => $validated['merchant_name'] ?? '',
                'merchant_biz_num' => $validated['merchant_biz_num'] ?? '',
                'deduction_type' => $validated['deduction_type'],
                'account_code' => $validated['account_code'] ?? null,
                'account_name' => $validated['account_name'] ?? null,
                'evidence_name' => $validated['evidence_name'] ?? '',
                'description' => $validated['description'] ?? '',
                'memo' => $validated['memo'] ?? '',
            ]);

            return response()->json([
                'success' => true,
                'message' => '수동 거래가 수정되었습니다.',
            ]);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'success' => false,
                'error' => '해당 거래를 찾을 수 없거나 수동 입력 건이 아닙니다.',
            ], 404);
        } catch (\Illuminate\Validation\ValidationException $e) {
            return response()->json([
                'success' => false,
                'error' => '입력 데이터가 올바르지 않습니다.',
                'errors' => $e->errors(),
            ], 422);
        } catch (\Throwable $e) {
            Log::error('수동 거래 수정 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '수정 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 수동 거래 삭제
     */
    public function destroyManual(int $id): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $transaction = CardTransaction::where('id', $id)
                ->where('tenant_id', $tenantId)
                ->where('is_manual', true)
                ->firstOrFail();

            $transaction->delete();

            return response()->json([
                'success' => true,
                'message' => '수동 거래가 삭제되었습니다.',
            ]);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'success' => false,
                'error' => '해당 거래를 찾을 수 없거나 수동 입력 건이 아닙니다.',
            ], 404);
        } catch (\Throwable $e) {
            Log::error('수동 거래 삭제 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '삭제 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 수동 입력 건을 로그 배열로 변환
     */
    private function convertManualToLogs($manualTransactions): array
    {
        $logs = [];
        $totalAmount = 0;
        $approvalCount = 0;
        $cancelCount = 0;
        $totalTax = 0;
        $deductibleAmount = 0;
        $deductibleCount = 0;
        $nonDeductibleAmount = 0;
        $nonDeductibleCount = 0;

        if (! $manualTransactions || $manualTransactions->isEmpty()) {
            return [
                'logs' => [],
                'summary' => [
                    'totalAmount' => 0, 'count' => 0, 'approvalCount' => 0, 'cancelCount' => 0,
                    'totalTax' => 0, 'deductibleAmount' => 0, 'deductibleCount' => 0,
                    'nonDeductibleAmount' => 0, 'nonDeductibleCount' => 0,
                ],
            ];
        }

        foreach ($manualTransactions as $t) {
            // 수동입력: approval_amount = 공급가액, tax = 부가세
            $supplyAmount = (float) $t->approval_amount;
            $tax = (float) $t->tax;
            $totalApproval = $supplyAmount + $tax; // 합계금액 = 공급가액 + 부가세
            $isApproval = $t->approval_type !== '2';
            $useDt = $t->use_dt;

            if ($isApproval) {
                $totalAmount += $totalApproval;
                $approvalCount++;
            } else {
                $cancelCount++;
            }

            $dateTime = '';
            if (! empty($useDt) && strlen($useDt) >= 8) {
                $dateTime = substr($useDt, 0, 4).'-'.substr($useDt, 4, 2).'-'.substr($useDt, 6, 2);
                if (strlen($useDt) >= 14) {
                    $dateTime .= ' '.substr($useDt, 8, 2).':'.substr($useDt, 10, 2).':'.substr($useDt, 12, 2);
                }
            }

            $deductionType = $t->deduction_type ?? 'non_deductible';
            $absAmount = abs($totalApproval);
            if ($deductionType === 'deductible') {
                $deductibleAmount += $absAmount;
                $deductibleCount++;
                $totalTax += abs($tax);
            } else {
                $nonDeductibleAmount += $absAmount;
                $nonDeductibleCount++;
            }

            $logs[] = [
                'cardNum' => $t->card_num,
                'cardNumMasked' => $this->maskCardNumber($t->card_num),
                'cardCompany' => $t->card_company,
                'cardBrand' => $t->card_company_name ?: $this->getCardCompanyName($t->card_company),
                'useDt' => $useDt,
                'useDate' => $t->use_date,
                'useTime' => $t->use_time,
                'useDateTime' => $dateTime,
                'approvalNum' => $t->approval_num ?? '',
                'approvalType' => $isApproval ? '1' : '2',
                'approvalTypeName' => $isApproval ? '승인' : '취소',
                'approvalAmount' => $totalApproval,
                'approvalAmountFormatted' => number_format($totalApproval),
                'tax' => $tax,
                'taxFormatted' => number_format($tax),
                'serviceCharge' => 0,
                'paymentPlan' => '0',
                'paymentPlanName' => '일시불',
                'currencyCode' => 'KRW',
                'merchantName' => $t->merchant_name ?? '',
                'merchantBizNum' => $t->merchant_biz_num ?? '',
                'merchantAddr' => $t->merchant_addr ?? '',
                'merchantCeo' => $t->merchant_ceo ?? '',
                'merchantBizType' => $t->merchant_biz_type ?? '',
                'merchantTel' => $t->merchant_tel ?? '',
                'memo' => $t->memo ?? '',
                'useKey' => $t->use_key ?? '',
                'accountCode' => $t->account_code ?? '',
                'accountName' => $t->account_name ?? '',
                'deductionType' => $deductionType,
                'evidenceName' => $t->evidence_name ?? '',
                'description' => $t->description ?? '',
                'isSaved' => true,
                'modifiedSupplyAmount' => null,
                'modifiedTax' => null,
                'effectiveSupplyAmount' => $supplyAmount,
                'effectiveTax' => $tax,
                'isAmountModified' => false,
                'isManual' => true,
                'dbId' => $t->id,
            ];
        }

        return [
            'logs' => $logs,
            'summary' => [
                'totalAmount' => $totalAmount,
                'count' => count($logs),
                'approvalCount' => $approvalCount,
                'cancelCount' => $cancelCount,
                'totalTax' => $totalTax,
                'deductibleAmount' => $deductibleAmount,
                'deductibleCount' => $deductibleCount,
                'nonDeductibleAmount' => $nonDeductibleAmount,
                'nonDeductibleCount' => $nonDeductibleCount,
            ],
        ];
    }

    /**
     * 두 summary 배열 합산
     */
    private function mergeSummaries(array $a, array $b): array
    {
        return [
            'totalAmount' => ($a['totalAmount'] ?? 0) + ($b['totalAmount'] ?? 0),
            'count' => ($a['count'] ?? 0) + ($b['count'] ?? 0),
            'approvalCount' => ($a['approvalCount'] ?? 0) + ($b['approvalCount'] ?? 0),
            'cancelCount' => ($a['cancelCount'] ?? 0) + ($b['cancelCount'] ?? 0),
            'totalTax' => ($a['totalTax'] ?? 0) + ($b['totalTax'] ?? 0),
            'deductibleAmount' => ($a['deductibleAmount'] ?? 0) + ($b['deductibleAmount'] ?? 0),
            'deductibleCount' => ($a['deductibleCount'] ?? 0) + ($b['deductibleCount'] ?? 0),
            'nonDeductibleAmount' => ($a['nonDeductibleAmount'] ?? 0) + ($b['nonDeductibleAmount'] ?? 0),
            'nonDeductibleCount' => ($a['nonDeductibleCount'] ?? 0) + ($b['nonDeductibleCount'] ?? 0),
        ];
    }

    /**
     * 숨김 키로 로그 필터링
     */
    private function filterHiddenLogs(array $parsed, $hiddenSet): array
    {
        $filteredLogs = [];
        $totalAmount = 0;
        $approvalCount = 0;
        $cancelCount = 0;
        $totalTax = 0;
        $deductibleAmount = 0;
        $deductibleCount = 0;
        $nonDeductibleAmount = 0;
        $nonDeductibleCount = 0;

        foreach ($parsed['logs'] as $log) {
            $uniqueKey = implode('|', [
                $log['cardNum'],
                $log['useDt'],
                $log['approvalNum'],
                (int) $log['approvalAmount'],
            ]);

            if ($hiddenSet->has($uniqueKey)) {
                continue;
            }

            $filteredLogs[] = $log;

            $amount = floatval($log['approvalAmount']);
            $isApproval = $log['approvalType'] !== '2';
            if ($isApproval) {
                $totalAmount += $amount;
                $approvalCount++;
            } else {
                $cancelCount++;
            }

            $deductionType = $log['deductionType'] ?? 'non_deductible';
            $absAmount = abs($amount);
            if ($deductionType === 'deductible') {
                $deductibleAmount += $absAmount;
                $deductibleCount++;
                $totalTax += abs($log['effectiveTax'] ?? $log['tax'] ?? 0);
            } else {
                $nonDeductibleAmount += $absAmount;
                $nonDeductibleCount++;
            }
        }

        return [
            'logs' => $filteredLogs,
            'summary' => [
                'totalAmount' => $totalAmount,
                'count' => count($filteredLogs),
                'approvalCount' => $approvalCount,
                'cancelCount' => $cancelCount,
                'totalTax' => $totalTax,
                'deductibleAmount' => $deductibleAmount,
                'deductibleCount' => $deductibleCount,
                'nonDeductibleAmount' => $nonDeductibleAmount,
                'nonDeductibleCount' => $nonDeductibleCount,
            ],
        ];
    }

    /**
     * 거래 숨김 처리
     */
    public function hideTransaction(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $uniqueKey = $request->input('uniqueKey');
            $originalData = $request->input('originalData', []);

            if (empty($uniqueKey)) {
                return response()->json([
                    'success' => false,
                    'error' => '고유키가 없습니다.',
                ]);
            }

            // 이미 숨김 처리된 건인지 확인
            $exists = CardTransactionHide::where('tenant_id', $tenantId)
                ->where('original_unique_key', $uniqueKey)
                ->exists();

            if ($exists) {
                return response()->json([
                    'success' => false,
                    'error' => '이미 숨김 처리된 거래입니다.',
                ]);
            }

            CardTransactionHide::hideTransaction($tenantId, $uniqueKey, $originalData, auth()->id());

            return response()->json([
                'success' => true,
                'message' => '거래가 숨김 처리되었습니다.',
            ]);
        } catch (\Throwable $e) {
            Log::error('거래 숨김 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '숨김 처리 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 거래 복원 (숨김 해제)
     */
    public function restoreTransaction(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $uniqueKey = $request->input('uniqueKey');

            if (empty($uniqueKey)) {
                return response()->json([
                    'success' => false,
                    'error' => '고유키가 없습니다.',
                ]);
            }

            $deleted = CardTransactionHide::restoreTransaction($tenantId, $uniqueKey);

            if ($deleted === 0) {
                return response()->json([
                    'success' => false,
                    'error' => '숨김 데이터를 찾을 수 없습니다.',
                ]);
            }

            return response()->json([
                'success' => true,
                'message' => '거래가 복원되었습니다.',
            ]);
        } catch (\Throwable $e) {
            Log::error('거래 복원 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '복원 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 숨김 처리된 거래 목록 조회
     */
    public function hiddenTransactions(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $startDate = $request->input('startDate', date('Ymd'));
            $endDate = $request->input('endDate', date('Ymd'));

            $hidden = CardTransactionHide::where('tenant_id', $tenantId)
                ->where('use_date', '>=', $startDate)
                ->where('use_date', '<=', $endDate)
                ->orderBy('use_date', 'desc')
                ->get()
                ->map(fn ($h) => [
                    'id' => $h->id,
                    'uniqueKey' => $h->original_unique_key,
                    'cardNum' => $h->card_num,
                    'useDate' => $h->use_date,
                    'approvalNum' => $h->approval_num,
                    'originalAmount' => (float) $h->original_amount,
                    'originalAmountFormatted' => number_format((float) $h->original_amount),
                    'merchantName' => $h->merchant_name,
                    'hiddenAt' => $h->created_at?->format('Y-m-d H:i'),
                ]);

            return response()->json([
                'success' => true,
                'data' => $hidden,
                'count' => $hidden->count(),
            ]);
        } catch (\Throwable $e) {
            Log::error('숨김 목록 조회 오류: '.$e->getMessage());

            return response()->json([
                'success' => false,
                'error' => '조회 오류: '.$e->getMessage(),
            ]);
        }
    }

    // ================================================================
    // 카드거래 복식부기 분개 API (journal_entries 통합)
    // ================================================================

    /**
     * 카드 거래 분개 생성/수정
     */
    public function storeJournal(Request $request): JsonResponse
    {
        $request->validate([
            'source_key' => 'required|string|max:255',
            'entry_date' => 'required|date',
            'description' => 'nullable|string|max:500',
            'lines' => 'required|array|min:2',
            'lines.*.dc_type' => 'required|in:debit,credit',
            'lines.*.account_code' => 'required|string|max:10',
            'lines.*.account_name' => 'required|string|max:100',
            'lines.*.debit_amount' => 'required|integer|min:0',
            'lines.*.credit_amount' => 'required|integer|min:0',
            'lines.*.trading_partner_id' => 'nullable|integer',
            'lines.*.trading_partner_name' => 'nullable|string|max:100',
            'lines.*.description' => 'nullable|string|max:300',
        ]);

        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $lines = $request->lines;

        $totalDebit = collect($lines)->sum('debit_amount');
        $totalCredit = collect($lines)->sum('credit_amount');

        if ($totalDebit !== $totalCredit || $totalDebit === 0) {
            return response()->json([
                'success' => false,
                'message' => '차변합계와 대변합계가 일치해야 하며 0보다 커야 합니다.',
            ], 422);
        }

        $existing = JournalEntry::getJournalBySourceKey($tenantId, 'ecard_transaction', $request->source_key);

        $maxRetries = 3;
        $lastError = null;

        for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
            try {
                $entry = DB::transaction(function () use ($tenantId, $request, $lines, $totalDebit, $totalCredit, $existing) {
                    if ($existing) {
                        // 수정 모드: 기존 lines 삭제 후 재생성 (전표번호 유지)
                        $existing->update([
                            'entry_date' => $request->entry_date,
                            'description' => $request->description,
                            'total_debit' => $totalDebit,
                            'total_credit' => $totalCredit,
                        ]);
                        $existing->lines()->delete();
                        $entry = $existing;
                    } else {
                        // 신규 생성
                        $entryNo = JournalEntry::generateEntryNo($tenantId, $request->entry_date);
                        $entry = JournalEntry::create([
                            'tenant_id' => $tenantId,
                            'entry_no' => $entryNo,
                            'entry_date' => $request->entry_date,
                            'description' => $request->description,
                            'total_debit' => $totalDebit,
                            'total_credit' => $totalCredit,
                            'status' => 'draft',
                            'source_type' => 'ecard_transaction',
                            'source_key' => $request->source_key,
                            'created_by_name' => auth()->user()?->name ?? '시스템',
                        ]);
                    }

                    foreach ($lines as $i => $line) {
                        JournalEntryLine::create([
                            'tenant_id' => $tenantId,
                            'journal_entry_id' => $entry->id,
                            'line_no' => $i + 1,
                            'dc_type' => $line['dc_type'],
                            'account_code' => $line['account_code'],
                            'account_name' => $line['account_name'],
                            'debit_amount' => $line['debit_amount'],
                            'credit_amount' => $line['credit_amount'],
                            'trading_partner_id' => $line['trading_partner_id'] ?? null,
                            'trading_partner_name' => $line['trading_partner_name'] ?? null,
                            'description' => $line['description'] ?? null,
                        ]);
                    }

                    return $entry;
                });

                return response()->json([
                    'success' => true,
                    'message' => $existing ? '분개가 수정되었습니다.' : '분개가 저장되었습니다.',
                    'data' => ['id' => $entry->id, 'entry_no' => $entry->entry_no],
                ]);
            } catch (\Illuminate\Database\QueryException $e) {
                $lastError = $e;
                if ($e->errorInfo[1] === 1062) {
                    continue;
                }
                break;
            } catch (\Throwable $e) {
                $lastError = $e;
                break;
            }
        }

        Log::error('카드거래 분개 저장 오류: '.$lastError->getMessage());

        return response()->json([
            'success' => false,
            'message' => '분개 저장 실패: '.$lastError->getMessage(),
        ], 500);
    }

    /**
     * 특정 카드 거래의 분개 조회
     */
    public function getJournal(Request $request): JsonResponse
    {
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $sourceKey = $request->get('source_key');

        if (! $sourceKey) {
            return response()->json(['success' => false, 'message' => 'source_key가 필요합니다.'], 422);
        }

        $entry = JournalEntry::forTenant($tenantId)
            ->where('source_type', 'ecard_transaction')
            ->where('source_key', $sourceKey)
            ->with('lines')
            ->first();

        if (! $entry) {
            return response()->json(['success' => true, 'data' => null]);
        }

        return response()->json([
            'success' => true,
            'data' => [
                'id' => $entry->id,
                'entry_no' => $entry->entry_no,
                'entry_date' => $entry->entry_date->format('Y-m-d'),
                'description' => $entry->description,
                'total_debit' => $entry->total_debit,
                'total_credit' => $entry->total_credit,
                'status' => $entry->status,
                'lines' => $entry->lines->map(fn ($line) => [
                    'id' => $line->id,
                    'line_no' => $line->line_no,
                    'dc_type' => $line->dc_type,
                    'account_code' => $line->account_code,
                    'account_name' => $line->account_name,
                    'debit_amount' => $line->debit_amount,
                    'credit_amount' => $line->credit_amount,
                    'trading_partner_id' => $line->trading_partner_id,
                    'trading_partner_name' => $line->trading_partner_name,
                    'description' => $line->description,
                ]),
            ],
        ]);
    }

    /**
     * 카드 거래 분개 삭제 (soft delete)
     */
    public function deleteJournal(int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

        $entry = JournalEntry::forTenant($tenantId)
            ->where('source_type', 'ecard_transaction')
            ->findOrFail($id);

        $entry->delete();

        return response()->json([
            'success' => true,
            'message' => '분개가 삭제되었습니다.',
        ]);
    }

    /**
     * 기간 내 카드 거래 분개 상태 일괄 조회
     */
    public function getJournalStatuses(Request $request): JsonResponse
    {
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $startDate = $request->input('startDate');
        $endDate = $request->input('endDate');

        if (! $startDate || ! $endDate) {
            return response()->json(['success' => false, 'message' => 'startDate, endDate가 필요합니다.'], 422);
        }

        // YYYYMMDD → YYYY-MM-DD 변환
        $startFormatted = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2);
        $endFormatted = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2);

        $journals = JournalEntry::where('tenant_id', $tenantId)
            ->where('source_type', 'ecard_transaction')
            ->whereBetween('entry_date', [$startFormatted, $endFormatted])
            ->select('id', 'source_key', 'entry_no')
            ->get();

        $map = [];
        foreach ($journals as $j) {
            $map[$j->source_key] = [
                'id' => $j->id,
                'entry_no' => $j->entry_no,
                'hasJournal' => true,
            ];
        }

        return response()->json([
            'success' => true,
            'data' => $map,
        ]);
    }

    /**
     * SOAP 호출
     */
    private function callSoap(string $method, array $params = []): array
    {
        if (! $this->soapClient) {
            return [
                'success' => false,
                'error' => '바로빌 SOAP 클라이언트가 초기화되지 않았습니다.',
            ];
        }

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

        if (empty($this->corpNum)) {
            return [
                'success' => false,
                'error' => '사업자번호가 설정되지 않았습니다.',
            ];
        }

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

        try {
            Log::info("바로빌 카드 API 호출 - Method: {$method}, CorpNum: {$this->corpNum}");

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

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

                // 에러 코드 체크
                if (is_numeric($resultData) && $resultData < 0) {
                    return [
                        'success' => false,
                        'error' => $this->getErrorMessage((int) $resultData),
                        'error_code' => (int) $resultData,
                    ];
                }

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

            return [
                'success' => true,
                'data' => $result,
            ];
        } catch (\SoapFault $e) {
            Log::error('바로빌 SOAP 오류: '.$e->getMessage());

            return [
                'success' => false,
                'error' => 'SOAP 오류: '.$e->getMessage(),
                'error_code' => $e->getCode(),
            ];
        } catch (\Throwable $e) {
            Log::error('바로빌 API 호출 오류: '.$e->getMessage());

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