<?php

namespace App\Http\Controllers\Barobill;

use App\Http\Controllers\Controller;
use App\Models\Barobill\BarobillConfig;
use App\Models\Barobill\BarobillMember;
use App\Models\Barobill\CardTransaction as BarobillCardTransaction;
use App\Models\Barobill\HometaxInvoice;
use App\Models\Barobill\HometaxInvoiceJournal;
use App\Models\Tenants\Tenant;
use App\Services\Barobill\HometaxSyncService;
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;

/**
 * 바로빌 홈택스 매출/매입 조회 컨트롤러
 *
 * 바로빌에서 발행/수신한 세금계산서 내역을 조회합니다.
 *
 * @see https://dev.barobill.co.kr/docs/taxinvoice
 */
class HometaxController extends Controller
{
    /**
     * 바로빌 설정
     */
    private ?string $certKey = null;

    private ?string $corpNum = null;

    private bool $isTestMode = false;

    private string $baseUrl = '';

    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';
            $this->baseUrl = $this->isTestMode
                ? 'https://testws.baroservice.com'
                : 'https://ws.baroservice.com';
        } 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->baseUrl = $this->isTestMode
                ? 'https://testws.baroservice.com'
                : 'https://ws.baroservice.com';
        }

        // SoapClient 초기화
        $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->baseUrl.'/TI.asmx?WSDL', [
                    '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('barobill.hometax.index'));
        }

        // 현재 선택된 테넌트 정보
        $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.hometax.index', [
            'certKey' => $this->certKey,
            'corpNum' => $this->corpNum,
            'isTestMode' => $isTestMode,
            'hasSoapClient' => $this->soapClient !== null,
            'tenantId' => $tenantId,
            '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;
            $this->baseUrl = $config->base_url ?: ($memberTestMode
                ? 'https://testws.baroservice.com'
                : 'https://ws.baroservice.com');

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

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

    /**
     * 매출 세금계산서 목록 조회 (GetPeriodTaxInvoiceSalesList)
     *
     * 바로빌 API 참고:
     * - TaxType: 1(과세+영세), 3(면세) 만 가능 (0은 미지원)
     * - DateType: 3(전송일자) 권장
     * - 전체 조회 시 1, 3을 각각 조회하여 합침
     */
    public function sales(Request $request): JsonResponse
    {
        try {
            $startDate = $request->input('startDate', date('Ymd', strtotime('-1 month')));
            $endDate = $request->input('endDate', date('Ymd'));
            $page = (int) $request->input('page', 1);
            $limit = (int) $request->input('limit', 50);
            $taxType = (int) $request->input('taxType', 0); // 0:전체, 1:과세+영세, 3:면세
            $dateType = (int) $request->input('dateType', 1); // 1:작성일자, 2:발급일자, 3:전송일자

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

            if (! $barobillMember) {
                return response()->json([
                    'success' => false,
                    'error' => '바로빌 회원사 정보가 없습니다. 테넌트 설정을 확인해주세요.',
                ]);
            }

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

            $userId = $barobillMember->barobill_id ?? '';

            if (empty($userId)) {
                return response()->json([
                    'success' => false,
                    'error' => '바로빌 사용자 ID가 설정되지 않았습니다.',
                ]);
            }

            // TaxType이 0(전체)인 경우 1(과세+영세)과 3(면세)를 각각 조회하여 합침
            $taxTypesToQuery = ($taxType === 0) ? [1, 3] : [$taxType];
            $allInvoices = [];
            $totalSummary = ['totalAmount' => 0, 'totalTax' => 0, 'totalSum' => 0, 'count' => 0];
            $lastPagination = ['currentPage' => $page, 'countPerPage' => $limit, 'maxPageNum' => 1, 'maxIndex' => 0];

            foreach ($taxTypesToQuery as $queryTaxType) {
                $result = $this->callSoap('GetPeriodTaxInvoiceSalesList', [
                    'UserID' => $userId,
                    'TaxType' => $queryTaxType, // 1: 과세+영세, 3: 면세
                    'DateType' => $dateType, // 1:작성일자, 2:발급일자, 3:전송일자
                    'StartDate' => $startDate,
                    'EndDate' => $endDate,
                    'CountPerPage' => $limit,
                    'CurrentPage' => $page,
                ]);

                if (! $result['success']) {
                    // 첫 번째 조회 실패 시 에러 반환
                    if (empty($allInvoices)) {
                        return response()->json([
                            'success' => false,
                            'error' => $result['error'],
                            'error_code' => $result['error_code'] ?? null,
                        ]);
                    }

                    continue; // 이미 일부 데이터가 있으면 계속 진행
                }

                $resultData = $result['data'];
                $errorCode = $this->checkErrorCode($resultData);

                // 에러 코드 체크 (데이터 없음 외의 에러)
                if ($errorCode && ! in_array($errorCode, [-60005, -60001])) {
                    if (empty($allInvoices)) {
                        return response()->json([
                            'success' => false,
                            'error' => $this->getErrorMessage($errorCode),
                            'error_code' => $errorCode,
                        ]);
                    }

                    continue;
                }

                // 데이터가 있는 경우 파싱
                if (! $errorCode || ! in_array($errorCode, [-60005, -60001])) {
                    $parsed = $this->parseInvoices($resultData, 'sales');
                    $allInvoices = array_merge($allInvoices, $parsed['invoices']);
                    $totalSummary['totalAmount'] += $parsed['summary']['totalAmount'];
                    $totalSummary['totalTax'] += $parsed['summary']['totalTax'];
                    $totalSummary['totalSum'] += $parsed['summary']['totalSum'] ?? ($parsed['summary']['totalAmount'] + $parsed['summary']['totalTax']);
                    $totalSummary['count'] += $parsed['summary']['count'];

                    // 페이지네이션 정보 업데이트 (마지막 조회 결과 사용)
                    $lastPagination = [
                        'currentPage' => $resultData->CurrentPage ?? 1,
                        'countPerPage' => $resultData->CountPerPage ?? 50,
                        'maxPageNum' => $resultData->MaxPageNum ?? 1,
                        'maxIndex' => $resultData->MaxIndex ?? 0,
                    ];
                }
            }

            // 작성일 기준으로 정렬 (최신순)
            usort($allInvoices, fn ($a, $b) => strcmp($b['writeDate'] ?? '', $a['writeDate'] ?? ''));

            // 마지막 매출 수집 시간 업데이트
            $barobillMember->update(['last_sales_fetch_at' => now()]);

            return response()->json([
                'success' => true,
                'data' => [
                    'invoices' => $allInvoices,
                    'pagination' => $lastPagination,
                    'summary' => $totalSummary,
                    'lastFetchAt' => now()->format('Y-m-d H:i:s'),
                ],
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 매출 조회 오류: '.$e->getMessage());

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

    /**
     * 매입 세금계산서 목록 조회 (GetPeriodTaxInvoicePurchaseList)
     *
     * 바로빌 API 참고:
     * - TaxType: 1(과세+영세), 3(면세) 만 가능 (0은 미지원)
     * - DateType: 3(전송일자) 권장
     * - 전체 조회 시 1, 3을 각각 조회하여 합침
     */
    public function purchases(Request $request): JsonResponse
    {
        try {
            $startDate = $request->input('startDate', date('Ymd', strtotime('-1 month')));
            $endDate = $request->input('endDate', date('Ymd'));
            $page = (int) $request->input('page', 1);
            $limit = (int) $request->input('limit', 50);
            $taxType = (int) $request->input('taxType', 0); // 0:전체, 1:과세+영세, 3:면세
            $dateType = (int) $request->input('dateType', 1); // 1:작성일자, 2:발급일자, 3:전송일자

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

            if (! $barobillMember) {
                return response()->json([
                    'success' => false,
                    'error' => '바로빌 회원사 정보가 없습니다. 테넌트 설정을 확인해주세요.',
                ]);
            }

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

            $userId = $barobillMember->barobill_id ?? '';

            if (empty($userId)) {
                return response()->json([
                    'success' => false,
                    'error' => '바로빌 사용자 ID가 설정되지 않았습니다.',
                ]);
            }

            // TaxType이 0(전체)인 경우 1(과세+영세)과 3(면세)를 각각 조회하여 합침
            $taxTypesToQuery = ($taxType === 0) ? [1, 3] : [$taxType];
            $allInvoices = [];
            $totalSummary = ['totalAmount' => 0, 'totalTax' => 0, 'totalSum' => 0, 'count' => 0];
            $lastPagination = ['currentPage' => $page, 'countPerPage' => $limit, 'maxPageNum' => 1, 'maxIndex' => 0];

            foreach ($taxTypesToQuery as $queryTaxType) {
                $result = $this->callSoap('GetPeriodTaxInvoicePurchaseList', [
                    'UserID' => $userId,
                    'TaxType' => $queryTaxType, // 1: 과세+영세, 3: 면세
                    'DateType' => $dateType, // 1:작성일자, 2:발급일자, 3:전송일자
                    'StartDate' => $startDate,
                    'EndDate' => $endDate,
                    'CountPerPage' => $limit,
                    'CurrentPage' => $page,
                ]);

                if (! $result['success']) {
                    // 첫 번째 조회 실패 시 에러 반환
                    if (empty($allInvoices)) {
                        return response()->json([
                            'success' => false,
                            'error' => $result['error'],
                            'error_code' => $result['error_code'] ?? null,
                        ]);
                    }

                    continue; // 이미 일부 데이터가 있으면 계속 진행
                }

                $resultData = $result['data'];
                $errorCode = $this->checkErrorCode($resultData);

                // 에러 코드 체크 (데이터 없음 외의 에러)
                if ($errorCode && ! in_array($errorCode, [-60005, -60001])) {
                    if (empty($allInvoices)) {
                        return response()->json([
                            'success' => false,
                            'error' => $this->getErrorMessage($errorCode),
                            'error_code' => $errorCode,
                        ]);
                    }

                    continue;
                }

                // 데이터가 있는 경우 파싱
                if (! $errorCode || ! in_array($errorCode, [-60005, -60001])) {
                    $parsed = $this->parseInvoices($resultData, 'purchase');
                    $allInvoices = array_merge($allInvoices, $parsed['invoices']);
                    $totalSummary['totalAmount'] += $parsed['summary']['totalAmount'];
                    $totalSummary['totalTax'] += $parsed['summary']['totalTax'];
                    $totalSummary['totalSum'] += $parsed['summary']['totalSum'] ?? ($parsed['summary']['totalAmount'] + $parsed['summary']['totalTax']);
                    $totalSummary['count'] += $parsed['summary']['count'];

                    // 페이지네이션 정보 업데이트 (마지막 조회 결과 사용)
                    $lastPagination = [
                        'currentPage' => $resultData->CurrentPage ?? 1,
                        'countPerPage' => $resultData->CountPerPage ?? 50,
                        'maxPageNum' => $resultData->MaxPageNum ?? 1,
                        'maxIndex' => $resultData->MaxIndex ?? 0,
                    ];
                }
            }

            // 작성일 기준으로 정렬 (최신순)
            usort($allInvoices, fn ($a, $b) => strcmp($b['writeDate'] ?? '', $a['writeDate'] ?? ''));

            // 마지막 매입 수집 시간 업데이트
            $barobillMember->update(['last_purchases_fetch_at' => now()]);

            return response()->json([
                'success' => true,
                'data' => [
                    'invoices' => $allInvoices,
                    'pagination' => $lastPagination,
                    'summary' => $totalSummary,
                    'lastFetchAt' => now()->format('Y-m-d H:i:s'),
                ],
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 매입 조회 오류: '.$e->getMessage());

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

    /**
     * 홈택스 스크래핑 서비스 등록 URL 조회
     *
     * 바로빌에서 홈택스 스크래핑을 신청하기 위한 URL을 반환합니다.
     */
    public function getScrapRequestUrl(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();

            if (! $barobillMember) {
                return response()->json([
                    'success' => false,
                    'error' => '바로빌 회원사 정보가 없습니다.',
                ]);
            }

            $userId = $barobillMember->barobill_id ?? '';

            $result = $this->callSoap('GetTaxInvoiceScrapRequestURL', [
                'UserID' => $userId,
            ]);

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

            // 결과가 URL 문자열이면 성공
            $url = $result['data'];
            if (is_string($url) && filter_var($url, FILTER_VALIDATE_URL)) {
                return response()->json([
                    'success' => true,
                    'data' => ['url' => $url],
                ]);
            }

            // 숫자면 에러코드
            if (is_numeric($url) && $url < 0) {
                return response()->json([
                    'success' => false,
                    'error' => $this->getErrorMessage((int) $url),
                    'error_code' => (int) $url,
                ]);
            }

            return response()->json([
                'success' => true,
                'data' => ['url' => (string) $url],
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 스크래핑 URL 조회 오류: '.$e->getMessage());

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

    /**
     * 홈택스 스크래핑 갱신 요청
     *
     * 홈택스에서 최신 데이터를 다시 수집하도록 요청합니다.
     */
    public function refreshScrap(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();

            if (! $barobillMember) {
                return response()->json([
                    'success' => false,
                    'error' => '바로빌 회원사 정보가 없습니다.',
                ]);
            }

            $userId = $barobillMember->barobill_id ?? '';

            $result = $this->callSoap('RefreshTaxInvoiceScrap', [
                'UserID' => $userId,
            ]);

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

            $code = $result['data'];
            if (is_numeric($code)) {
                if ($code < 0) {
                    return response()->json([
                        'success' => false,
                        'error' => $this->getErrorMessage((int) $code),
                        'error_code' => (int) $code,
                    ]);
                }

                return response()->json([
                    'success' => true,
                    'message' => '홈택스 데이터 수집이 요청되었습니다. 잠시 후 다시 조회해주세요.',
                ]);
            }

            return response()->json([
                'success' => true,
                'message' => '홈택스 데이터 수집이 요청되었습니다.',
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 스크래핑 갱신 오류: '.$e->getMessage());

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

    /**
     * 서비스 상태 진단
     *
     * 바로빌 API 연결 및 홈택스 서비스 상태를 확인합니다.
     */
    public function diagnose(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);
            }

            $userId = $barobillMember?->barobill_id ?? '';
            $memberCorpNum = $barobillMember?->biz_no ?? '';

            $diagnostics = [
                'config' => [
                    'certKey' => ! empty($this->certKey) ? substr($this->certKey, 0, 8).'...' : '미설정',
                    'corpNum' => $this->corpNum ?? '미설정',  // 파트너사 사업자번호 (API 인증용)
                    'isTestMode' => $this->isTestMode,
                    'baseUrl' => $this->baseUrl,
                ],
                'member' => [
                    'userId' => $userId ?: '미설정',  // 테넌트의 바로빌 ID (API 호출에 사용)
                    'bizNo' => $memberCorpNum ?: '미설정',  // 테넌트 사업자번호 (참고용)
                    'corpName' => $barobillMember?->corp_name ?? '미설정',
                ],
                'tests' => [],
            ];

            // 테스트 1: 홈택스 스크래핑 URL 조회 (서비스 활성화 확인용)
            $scrapUrlResult = $this->callSoap('GetTaxInvoiceScrapRequestURL', [
                'UserID' => $userId,
            ]);
            $diagnostics['tests']['scrapRequestUrl'] = [
                'method' => 'GetTaxInvoiceScrapRequestURL',
                'success' => $scrapUrlResult['success'],
                'result' => $scrapUrlResult['success']
                    ? (is_string($scrapUrlResult['data']) ? '성공 (URL 반환)' : $scrapUrlResult['data'])
                    : ($scrapUrlResult['error'] ?? '오류'),
            ];

            // 테스트 2: 매출 세금계산서 조회 (기간: 최근 1개월)
            // TaxType: 1(과세+영세), 3(면세) 만 가능 / DateType: 3(전송일자) 권장
            $salesResult = $this->callSoap('GetPeriodTaxInvoiceSalesList', [
                'UserID' => $userId,
                'TaxType' => 1, // 1: 과세+영세 (0은 미지원)
                'DateType' => 3, // 3: 전송일자 기준 (권장)
                'StartDate' => date('Ymd', strtotime('-1 month')),
                'EndDate' => date('Ymd'),
                'CountPerPage' => 1,
                'CurrentPage' => 1,
            ]);
            $diagnostics['tests']['salesList'] = [
                'method' => 'GetPeriodTaxInvoiceSalesList',
                'success' => $salesResult['success'],
                'result' => $salesResult['success']
                    ? ($this->checkErrorCode($salesResult['data'])
                        ? $this->getErrorMessage($this->checkErrorCode($salesResult['data']))
                        : '성공')
                    : ($salesResult['error'] ?? '오류'),
            ];

            // 테스트 3: 잔액 조회 (기본 연결 및 인증 확인용)
            $balanceResult = $this->callSoap('GetBalanceCostAmount', []);
            $diagnostics['tests']['balance'] = [
                'method' => 'GetBalanceCostAmount',
                'success' => $balanceResult['success'],
                'result' => $balanceResult['success']
                    ? (is_numeric($balanceResult['data']) && $balanceResult['data'] >= 0
                        ? '성공 (잔액: '.number_format($balanceResult['data']).'원)'
                        : $balanceResult['data'])
                    : ($balanceResult['error'] ?? '오류'),
            ];

            return response()->json([
                'success' => true,
                'data' => $diagnostics,
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 서비스 진단 오류: '.$e->getMessage());

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

    /**
     * 홈택스 수집 요청 (미지원 안내)
     */
    public function requestCollect(Request $request): JsonResponse
    {
        // 홈택스 스크래핑 갱신으로 대체
        return $this->refreshScrap($request);
    }

    /**
     * 수집 상태 확인 (마지막 수집 시간 조회)
     */
    public function collectStatus(Request $request): JsonResponse
    {
        $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
        $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();

        $salesLastFetch = $barobillMember?->last_sales_fetch_at;
        $purchasesLastFetch = $barobillMember?->last_purchases_fetch_at;

        return response()->json([
            'success' => true,
            'data' => [
                'salesLastCollectDate' => $salesLastFetch ? $salesLastFetch->format('Y-m-d H:i') : '',
                'purchaseLastCollectDate' => $purchasesLastFetch ? $purchasesLastFetch->format('Y-m-d H:i') : '',
                'isCollecting' => false,
                'collectStateText' => ($salesLastFetch || $purchasesLastFetch) ? '조회 완료' : '조회 전',
                'message' => '매출/매입 탭을 클릭하면 데이터가 조회되고 수집 시간이 기록됩니다.',
            ],
        ]);
    }

    /**
     * SOAP 객체에서 안전하게 속성 가져오기
     */
    private function getProperty(object $obj, string $prop, mixed $default = ''): mixed
    {
        return property_exists($obj, $prop) ? $obj->$prop : $default;
    }

    /**
     * 세금계산서 파싱 (PagedTaxInvoiceEx -> SimpleTaxInvoiceEx)
     */
    private function parseInvoices($resultData, string $type = 'sales'): array
    {
        $invoices = [];
        $totalAmount = 0;
        $totalTax = 0;

        $rawList = [];

        // PagedTaxInvoiceEx 응답 구조: SimpleTaxInvoiceExList -> SimpleTaxInvoiceEx
        if (isset($resultData->SimpleTaxInvoiceExList) && isset($resultData->SimpleTaxInvoiceExList->SimpleTaxInvoiceEx)) {
            $rawList = is_array($resultData->SimpleTaxInvoiceExList->SimpleTaxInvoiceEx)
                ? $resultData->SimpleTaxInvoiceExList->SimpleTaxInvoiceEx
                : [$resultData->SimpleTaxInvoiceExList->SimpleTaxInvoiceEx];
        }

        foreach ($rawList as $item) {
            // SimpleTaxInvoiceEx는 AmountTotal, TaxTotal, TotalAmount 사용
            $supplyAmount = floatval($this->getProperty($item, 'AmountTotal', 0));
            $taxAmount = floatval($this->getProperty($item, 'TaxTotal', 0));
            $total = floatval($this->getProperty($item, 'TotalAmount', 0));
            if ($total == 0) {
                $total = $supplyAmount + $taxAmount;
            }
            $totalAmount += $supplyAmount;
            $totalTax += $taxAmount;

            // 날짜 포맷팅 - WriteDate 또는 IssueDT 사용
            $writeDate = $this->getProperty($item, 'WriteDate', '');
            if (empty($writeDate)) {
                $writeDate = $this->getProperty($item, 'IssueDT', '');
            }
            $formattedDate = '';
            if (! empty($writeDate) && strlen($writeDate) >= 8) {
                $formattedDate = substr($writeDate, 0, 4).'-'.substr($writeDate, 4, 2).'-'.substr($writeDate, 6, 2);
            }

            // 과세유형 (int: 1=과세, 2=영세, 3=면세)
            $taxType = $this->getProperty($item, 'TaxType', '');

            // 영수/청구 (int: 1=영수, 2=청구)
            $purposeType = $this->getProperty($item, 'PurposeType', '');

            $invoices[] = [
                'ntsConfirmNum' => $this->getProperty($item, 'NTSSendKey', ''),
                'writeDate' => $writeDate,
                'writeDateFormatted' => $formattedDate,
                'issueDT' => $this->getProperty($item, 'IssueDT', ''),
                'invoicerCorpNum' => $this->getProperty($item, 'InvoicerCorpNum', ''),
                'invoicerCorpName' => $this->getProperty($item, 'InvoicerCorpName', ''),
                'invoicerCEOName' => $this->getProperty($item, 'InvoicerCEOName', ''),
                'invoiceeCorpNum' => $this->getProperty($item, 'InvoiceeCorpNum', ''),
                'invoiceeCorpName' => $this->getProperty($item, 'InvoiceeCorpName', ''),
                'invoiceeCEOName' => $this->getProperty($item, 'InvoiceeCEOName', ''),
                'supplyAmount' => $supplyAmount,
                'supplyAmountFormatted' => number_format($supplyAmount),
                'taxAmount' => $taxAmount,
                'taxAmountFormatted' => number_format($taxAmount),
                'totalAmount' => $total,
                'totalAmountFormatted' => number_format($total),
                'taxType' => $taxType,
                'taxTypeName' => $this->getTaxTypeName($taxType),
                'purposeType' => $purposeType,
                'purposeTypeName' => $this->getPurposeTypeName($purposeType),
                'modifyCode' => $this->getProperty($item, 'ModifyCode', ''),
                'remark' => $this->getProperty($item, 'Remark1', ''),
                'itemName' => $this->getProperty($item, 'ItemName', ''),
            ];
        }

        return [
            'invoices' => $invoices,
            'summary' => [
                'totalAmount' => $totalAmount,
                'totalTax' => $totalTax,
                'totalSum' => $totalAmount + $totalTax,
                'count' => count($invoices),
            ],
        ];
    }

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

        return null;
    }

    /**
     * 에러 메시지 반환
     */
    private function getErrorMessage(int $errorCode): string
    {
        $messages = [
            -10002 => '인증 실패 (-10002). CERTKEY가 올바르지 않거나 만료되었습니다.',
            -10008 => '날짜형식이 잘못되었습니다 (-10008). 날짜는 YYYYMMDD 형식(하이픈 제외)으로 입력해주세요.',
            -11010 => '과세형태(TaxType)가 잘못되었습니다 (-11010). TaxType은 1(과세+영세) 또는 3(면세)만 가능합니다.',
            -24005 => 'UserID가 필요합니다 (-24005). 바로빌 회원사 ID를 설정해주세요.',
            -24006 => '조회된 데이터가 없습니다 (-24006).',
            -25005 => '조회된 데이터가 없습니다 (-25005).',
            -26012 => '홈택스 스크래핑 서비스 미신청 (-26012). 바로빌에서 홈택스 매입매출 스크래핑 서비스 신청이 필요합니다.',
            -60001 => '등록된 홈택스 정보가 없습니다 (-60001). 바로빌에서 홈택스 연동 설정이 필요합니다.',
            -60002 => '홈택스 인증서가 등록되지 않았습니다 (-60002). 바로빌에서 홈택스 인증서 등록이 필요합니다.',
            -60003 => '홈택스 수집 서비스가 활성화되지 않았습니다 (-60003). 바로빌에서 서비스 신청이 필요합니다.',
            -60004 => '홈택스 부서사용자 ID가 등록되지 않았습니다 (-60004). 바로빌에서 홈택스 연동 설정이 필요합니다.',
            -60005 => '조회된 데이터가 없습니다 (-60005). 해당 기간에 세금계산서가 없습니다.',
            -60010 => '홈택스 로그인 실패 (-60010). 부서사용자 ID/비밀번호를 확인해주세요.',
            -60011 => '홈택스 데이터 수집 중입니다 (-60011). 잠시 후 다시 조회해주세요.',
        ];

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

    /**
     * 수집 유형 코드 반환
     */
    private function getCollectTypeCode(string $type): int
    {
        return match ($type) {
            'sales' => 1,
            'purchase' => 2,
            default => 0 // all
        };
    }

    /**
     * 과세유형 코드 -> 명칭 (1:과세, 2:영세, 3:면세)
     */
    private function getTaxTypeName(mixed $code): string
    {
        $code = (string) $code;

        return match ($code) {
            '1', '01' => '과세',
            '2', '02' => '영세',
            '3', '03' => '면세',
            default => $code ?: '-'
        };
    }

    /**
     * 발급유형 코드 -> 명칭
     */
    private function getIssueTypeName(mixed $code): string
    {
        $code = (string) $code;

        return match ($code) {
            '1', '01' => '정발행',
            '2', '02' => '역발행',
            '3', '03' => '위수탁',
            default => $code ?: '-'
        };
    }

    /**
     * 영수/청구 코드 -> 명칭 (1:영수, 2:청구)
     */
    private function getPurposeTypeName(mixed $code): string
    {
        $code = (string) $code;

        return match ($code) {
            '1', '01' => '영수',
            '2', '02' => '청구',
            default => $code ?: '-'
        };
    }

    /**
     * 수집 상태 파싱
     */
    private function parseCollectState($data): array
    {
        return [
            'salesLastCollectDate' => $data->SalesLastCollectDate ?? '',
            'purchaseLastCollectDate' => $data->PurchaseLastCollectDate ?? '',
            'isCollecting' => ($data->CollectState ?? 0) == 1,
            'collectStateText' => ($data->CollectState ?? 0) == 1 ? '수집 중' : '대기',
        ];
    }

    /**
     * 엑셀 다운로드
     */
    public function exportExcel(Request $request): StreamedResponse|JsonResponse
    {
        try {
            $type = $request->input('type', 'sales'); // sales or purchase
            $invoices = $request->input('invoices', []);

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

            $typeName = $type === 'sales' ? '매출' : '매입';
            $filename = "홈택스_{$typeName}_".date('Ymd_His').'.csv';

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

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

                // 헤더
                if ($type === 'sales') {
                    fputcsv($handle, [
                        '작성일', '국세청승인번호', '공급받는자 사업자번호', '공급받는자 상호',
                        '공급가액', '세액', '합계', '과세유형', '발급유형', '영수/청구',
                    ]);
                } else {
                    fputcsv($handle, [
                        '작성일', '국세청승인번호', '공급자 사업자번호', '공급자 상호',
                        '공급가액', '세액', '합계', '과세유형', '발급유형', '영수/청구',
                    ]);
                }

                // 데이터
                foreach ($invoices as $inv) {
                    if ($type === 'sales') {
                        fputcsv($handle, [
                            $inv['writeDateFormatted'] ?? '',
                            $inv['ntsConfirmNum'] ?? '',
                            $inv['invoiceeCorpNum'] ?? '',
                            $inv['invoiceeCorpName'] ?? '',
                            $inv['supplyAmount'] ?? 0,
                            $inv['taxAmount'] ?? 0,
                            $inv['totalAmount'] ?? 0,
                            $inv['taxTypeName'] ?? '',
                            $inv['issueTypeName'] ?? '',
                            $inv['purposeTypeName'] ?? '',
                        ]);
                    } else {
                        fputcsv($handle, [
                            $inv['writeDateFormatted'] ?? '',
                            $inv['ntsConfirmNum'] ?? '',
                            $inv['invoicerCorpNum'] ?? '',
                            $inv['invoicerCorpName'] ?? '',
                            $inv['supplyAmount'] ?? 0,
                            $inv['taxAmount'] ?? 0,
                            $inv['totalAmount'] ?? 0,
                            $inv['taxTypeName'] ?? '',
                            $inv['issueTypeName'] ?? '',
                            $inv['purposeTypeName'] ?? '',
                        ]);
                    }
                }

                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(),
            ]);
        }
    }

    /**
     * SoapClient를 통한 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 {
            // 날짜 파라미터 로깅 (디버깅용)
            $dateInfo = '';
            if (isset($params['StartDate']) || isset($params['EndDate'])) {
                $dateInfo = ', StartDate: '.($params['StartDate'] ?? 'N/A').', EndDate: '.($params['EndDate'] ?? 'N/A');
            }
            Log::info("바로빌 홈택스 API 호출 (SoapClient) - Method: {$method}, CorpNum: ".($params['CorpNum'] ?? 'N/A').', UserID: '.($params['UserID'] ?? 'N/A').', CERTKEY: '.substr($params['CERTKEY'] ?? '', 0, 10).'...'.$dateInfo);

            // SoapClient로 호출
            $result = $this->soapClient->$method($params);
            $resultProperty = $method.'Result';

            if (! isset($result->$resultProperty)) {
                return [
                    'success' => false,
                    'error' => '응답 결과를 찾을 수 없습니다.',
                ];
            }

            $resultData = $result->$resultProperty;

            // 단순 숫자 응답인 경우 (에러 코드 또는 성공 코드)
            if (is_numeric($resultData)) {
                $code = (int) $resultData;
                if ($code < 0) {
                    return [
                        'success' => false,
                        'error' => $this->getErrorMessage($code),
                        'error_code' => $code,
                    ];
                }

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

            // 문자열 응답 (URL 등)
            if (is_string($resultData)) {
                return [
                    'success' => true,
                    'data' => $resultData,
                ];
            }

            // 객체 응답 (목록 조회 등)
            return [
                'success' => true,
                'data' => $resultData,
            ];

        } catch (\SoapFault $e) {
            Log::error('바로빌 SOAP 오류: '.$e->getMessage());

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

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

    /**
     * SOAP 요청 XML 생성
     */
    private function buildSoapRequest(string $method, array $params): string
    {
        $paramsXml = '';
        foreach ($params as $key => $value) {
            $paramsXml .= "<{$key}>".htmlspecialchars((string) $value, ENT_XML1, 'UTF-8')."</{$key}>";
        }

        return '<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:bar="http://ws.baroservice.com/">
  <soap:Body>
    <bar:'.$method.'>
      '.$paramsXml.'
    </bar:'.$method.'>
  </soap:Body>
</soap:Envelope>';
    }

    /**
     * SOAP 응답 XML 파싱
     */
    private function parseSoapResponse(string $xmlResponse, string $method): array
    {
        try {
            // XML 파싱
            $xml = simplexml_load_string($xmlResponse, 'SimpleXMLElement', LIBXML_NOCDATA);
            if ($xml === false) {
                return [
                    'success' => false,
                    'error' => 'XML 파싱 실패',
                ];
            }

            // 네임스페이스 등록
            $xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
            $xml->registerXPathNamespace('bar', 'http://ws.baroservice.com/');

            // 결과 노드 찾기
            $resultNodes = $xml->xpath("//bar:{$method}Response/bar:{$method}Result");

            if (empty($resultNodes)) {
                // 네임스페이스 없이 다시 시도
                $resultNodes = $xml->xpath("//*[local-name()='{$method}Response']/*[local-name()='{$method}Result']");
            }

            if (empty($resultNodes)) {
                Log::warning("응답에서 {$method}Result를 찾을 수 없음");

                return [
                    'success' => false,
                    'error' => '응답 결과를 찾을 수 없습니다.',
                ];
            }

            $resultNode = $resultNodes[0];

            // 단순 숫자 응답인 경우 (에러 코드)
            $textContent = trim((string) $resultNode);
            if (is_numeric($textContent) && $resultNode->count() === 0) {
                $code = (int) $textContent;
                if ($code < 0) {
                    return [
                        'success' => false,
                        'error' => $this->getErrorMessage($code),
                        'error_code' => $code,
                    ];
                }

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

            // 복잡한 객체 응답 파싱
            $resultData = $this->xmlToObject($resultNode);

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

        } catch (\Throwable $e) {
            Log::error('SOAP 응답 파싱 오류: '.$e->getMessage());

            return [
                'success' => false,
                'error' => 'XML 파싱 오류: '.$e->getMessage(),
            ];
        }
    }

    /**
     * SimpleXMLElement를 stdClass로 변환
     */
    private function xmlToObject(\SimpleXMLElement $xml): object
    {
        $result = new \stdClass;

        // 속성 처리
        foreach ($xml->attributes() as $attrName => $attrValue) {
            $result->$attrName = (string) $attrValue;
        }

        // 자식 요소 처리
        $children = $xml->children();
        $childNames = [];

        foreach ($children as $name => $child) {
            $childNames[$name] = ($childNames[$name] ?? 0) + 1;
        }

        foreach ($children as $name => $child) {
            if ($childNames[$name] > 1) {
                // 여러 개의 동일 이름 요소 → 배열
                if (! isset($result->$name)) {
                    $result->$name = [];
                }
                if ($child->count() > 0) {
                    $result->{$name}[] = $this->xmlToObject($child);
                } else {
                    $result->{$name}[] = (string) $child;
                }
            } elseif ($child->count() > 0) {
                // 자식이 있는 요소 → 재귀 호출
                $result->$name = $this->xmlToObject($child);
            } else {
                // 텍스트 노드
                $result->$name = (string) $child;
            }
        }

        return $result;
    }

    /**
     * 로컬 DB에서 매출 세금계산서 조회
     */
    public function localSales(Request $request, HometaxSyncService $syncService): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $startDate = $request->input('startDate');
            $endDate = $request->input('endDate');

            // YYYYMMDD 형식을 Y-m-d로 변환
            if (strlen($startDate) === 8) {
                $startDate = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2);
            }
            if (strlen($endDate) === 8) {
                $endDate = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2);
            }

            $dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue';
            $searchCorp = $request->input('searchCorp');

            $data = $syncService->getLocalInvoices(
                $tenantId,
                'sales',
                $startDate,
                $endDate,
                $dateType,
                $searchCorp
            );

            return response()->json([
                'success' => true,
                'data' => $data,
                'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'sales'),
            ]);
        } catch (\Throwable $e) {
            Log::error('로컬 매출 조회 오류: '.$e->getMessage());

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

    /**
     * 로컬 DB에서 매입 세금계산서 조회
     */
    public function localPurchases(Request $request, HometaxSyncService $syncService): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $startDate = $request->input('startDate');
            $endDate = $request->input('endDate');

            // YYYYMMDD 형식을 Y-m-d로 변환
            if (strlen($startDate) === 8) {
                $startDate = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2);
            }
            if (strlen($endDate) === 8) {
                $endDate = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2);
            }

            $dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue';
            $searchCorp = $request->input('searchCorp');

            $data = $syncService->getLocalInvoices(
                $tenantId,
                'purchase',
                $startDate,
                $endDate,
                $dateType,
                $searchCorp
            );

            return response()->json([
                'success' => true,
                'data' => $data,
                'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'purchase'),
            ]);
        } catch (\Throwable $e) {
            Log::error('로컬 매입 조회 오류: '.$e->getMessage());

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

    /**
     * 바로빌 API에서 데이터를 가져와 로컬 DB에 동기화
     */
    public function sync(Request $request, HometaxSyncService $syncService): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $type = $request->input('type', 'all'); // 'sales', 'purchase', 'all'
            $startDate = $request->input('startDate', date('Ymd', strtotime('-1 month')));
            $endDate = $request->input('endDate', date('Ymd'));
            $dateType = (int) $request->input('dateType', 1);

            $results = [];

            // 매출 동기화
            if ($type === 'all' || $type === 'sales') {
                $salesRequest = new Request([
                    'startDate' => $startDate,
                    'endDate' => $endDate,
                    'dateType' => $dateType,
                    'limit' => 500,
                ]);

                $salesResponse = $this->sales($salesRequest);
                $salesData = json_decode($salesResponse->getContent(), true);

                if ($salesData['success'] && ! empty($salesData['data']['invoices'])) {
                    $results['sales'] = $syncService->syncInvoices(
                        $salesData['data']['invoices'],
                        $tenantId,
                        'sales'
                    );
                } else {
                    $results['sales'] = [
                        'inserted' => 0,
                        'updated' => 0,
                        'failed' => 0,
                        'total' => 0,
                        'error' => $salesData['error'] ?? null,
                    ];
                }
            }

            // 매입 동기화
            if ($type === 'all' || $type === 'purchase') {
                $purchaseRequest = new Request([
                    'startDate' => $startDate,
                    'endDate' => $endDate,
                    'dateType' => $dateType,
                    'limit' => 500,
                ]);

                $purchaseResponse = $this->purchases($purchaseRequest);
                $purchaseData = json_decode($purchaseResponse->getContent(), true);

                if ($purchaseData['success'] && ! empty($purchaseData['data']['invoices'])) {
                    $results['purchase'] = $syncService->syncInvoices(
                        $purchaseData['data']['invoices'],
                        $tenantId,
                        'purchase'
                    );
                } else {
                    $results['purchase'] = [
                        'inserted' => 0,
                        'updated' => 0,
                        'failed' => 0,
                        'total' => 0,
                        'error' => $purchaseData['error'] ?? null,
                    ];
                }
            }

            // 총 결과 계산
            $totalInserted = ($results['sales']['inserted'] ?? 0) + ($results['purchase']['inserted'] ?? 0);
            $totalUpdated = ($results['sales']['updated'] ?? 0) + ($results['purchase']['updated'] ?? 0);

            return response()->json([
                'success' => true,
                'message' => "동기화 완료: {$totalInserted}건 추가, {$totalUpdated}건 갱신",
                'data' => $results,
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 동기화 오류: '.$e->getMessage());

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

    /**
     * 자동 증분 동기화
     * 마지막 동기화 시점 이후의 데이터만 바로빌 API에서 가져와 로컬 DB에 저장
     */
    public function autoSync(Request $request, HometaxSyncService $syncService): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $type = $request->input('type', 'sales'); // 'sales' 또는 'purchase'
            $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();

            if (! $barobillMember) {
                return response()->json(['success' => true, 'skipped' => true, 'reason' => 'no_member']);
            }

            // 마지막 동기화 시간 확인
            $lastFetchField = $type === 'sales' ? 'last_sales_fetch_at' : 'last_purchases_fetch_at';
            $lastFetch = $barobillMember->$lastFetchField;

            // 10분 이내에 이미 동기화했으면 스킵
            if ($lastFetch && $lastFetch->diffInMinutes(now()) < 10) {
                return response()->json([
                    'success' => true,
                    'skipped' => true,
                    'reason' => 'recent_sync',
                    'lastSyncAt' => $syncService->getLastSyncTime($tenantId, $type),
                    'lastFetchAt' => $lastFetch->format('Y-m-d H:i:s'),
                ]);
            }

            // 증분 범위 계산: 마지막 동기화일 -3일 ~ 오늘
            // 전송일자(dateType=3) 기준으로 조회하여 작성일자가 오래된 신규 전송건도 포착
            $startDate = $lastFetch
                ? $lastFetch->copy()->subDays(3)->format('Ymd')
                : date('Ymd', strtotime('-1 month'));
            $endDate = date('Ymd');

            // 기존 sync 로직 재사용: API 호출 → DB 저장
            $apiMethod = $type === 'sales' ? 'sales' : 'purchases';
            $apiRequest = new Request([
                'startDate' => $startDate,
                'endDate' => $endDate,
                'dateType' => 3, // 전송일자 기준 (바로빌 권장, 신규 전송건 누락 방지)
                'limit' => 500,
            ]);

            $apiResponse = $this->$apiMethod($apiRequest);
            $apiData = json_decode($apiResponse->getContent(), true);

            $syncResult = ['inserted' => 0, 'updated' => 0, 'total' => 0];

            if ($apiData['success'] && ! empty($apiData['data']['invoices'])) {
                $syncResult = $syncService->syncInvoices(
                    $apiData['data']['invoices'],
                    $tenantId,
                    $type
                );
            }

            return response()->json([
                'success' => true,
                'skipped' => false,
                'data' => $syncResult,
                'hasNewData' => ($syncResult['inserted'] ?? 0) > 0,
                'lastSyncAt' => $syncService->getLastSyncTime($tenantId, $type),
                'lastFetchAt' => now()->format('Y-m-d H:i:s'),
            ]);
        } catch (\Throwable $e) {
            Log::error('홈택스 자동동기화 오류: '.$e->getMessage());

            // 자동동기화 실패는 치명적이지 않음 - 로컬 데이터는 정상 표시됨
            return response()->json([
                'success' => true,
                'skipped' => true,
                'reason' => 'error',
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * 세금계산서 메모 업데이트
     */
    public function updateMemo(Request $request, HometaxSyncService $syncService): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $id = $request->input('id');
            $memo = $request->input('memo');

            $success = $syncService->updateMemo($id, $tenantId, $memo);

            return response()->json([
                'success' => $success,
                'message' => $success ? '메모가 저장되었습니다.' : '저장에 실패했습니다.',
            ]);
        } catch (\Throwable $e) {
            return response()->json([
                'success' => false,
                'error' => '오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * 세금계산서 확인 여부 토글
     */
    public function toggleChecked(Request $request, HometaxSyncService $syncService): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $id = $request->input('id');

            $success = $syncService->toggleChecked($id, $tenantId);

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

    /**
     * 수동 세금계산서 저장
     */
    public function manualStore(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $validated = $request->validate([
                'invoice_type' => 'required|in:sales,purchase',
                'write_date' => 'required|date',
                'invoicer_corp_name' => 'nullable|string|max:200',
                'invoicer_corp_num' => 'nullable|string|max:20',
                'invoicer_ceo_name' => 'nullable|string|max:50',
                'invoicer_branch_num' => 'nullable|string|max:10',
                'invoicer_address' => 'nullable|string|max:300',
                'invoicer_biz_type' => 'nullable|string|max:100',
                'invoicer_biz_class' => 'nullable|string|max:100',
                'invoicer_email' => 'nullable|string|max:200',
                'invoicer_email2' => 'nullable|string|max:200',
                'invoicee_corp_name' => 'nullable|string|max:200',
                'invoicee_corp_num' => 'nullable|string|max:20',
                'invoicee_ceo_name' => 'nullable|string|max:50',
                'invoicee_branch_num' => 'nullable|string|max:10',
                'invoicee_address' => 'nullable|string|max:300',
                'invoicee_biz_type' => 'nullable|string|max:100',
                'invoicee_biz_class' => 'nullable|string|max:100',
                'invoicee_email' => 'nullable|string|max:200',
                'invoicee_email2' => 'nullable|string|max:200',
                'supply_amount' => 'required|numeric|min:0',
                'tax_amount' => 'nullable|numeric|min:0',
                'item_name' => 'nullable|string|max:200',
                'remark' => 'nullable|string|max:500',
                'tax_type' => 'nullable|integer|in:1,2,3',
                'purpose_type' => 'nullable|integer|in:1,2',
            ], [
                'invoice_type.required' => '매출/매입 구분은 필수입니다.',
                'invoice_type.in' => '매출/매입 구분값이 올바르지 않습니다.',
                'write_date.required' => '작성일자는 필수입니다.',
                'write_date.date' => '작성일자 형식이 올바르지 않습니다.',
                'supply_amount.required' => '공급가액은 필수입니다.',
                'supply_amount.numeric' => '공급가액은 숫자여야 합니다.',
                'supply_amount.min' => '공급가액은 0 이상이어야 합니다.',
                'tax_amount.numeric' => '세액은 숫자여야 합니다.',
                'tax_amount.min' => '세액은 0 이상이어야 합니다.',
            ]);

            // MAN-YYYYMMDD-NNN 형식 자동채번
            $dateStr = date('Ymd', strtotime($validated['write_date']));
            $lastNum = HometaxInvoice::where('tenant_id', $tenantId)
                ->where('nts_confirm_num', 'like', "MAN-{$dateStr}-%")
                ->orderByRaw('CAST(SUBSTRING_INDEX(nts_confirm_num, "-", -1) AS UNSIGNED) DESC')
                ->value('nts_confirm_num');

            $seq = 1;
            if ($lastNum) {
                $parts = explode('-', $lastNum);
                $seq = (int) end($parts) + 1;
            }
            $ntsConfirmNum = sprintf('MAN-%s-%03d', $dateStr, $seq);

            $taxAmount = (float) ($validated['tax_amount'] ?? 0);
            $totalAmount = (float) $validated['supply_amount'] + $taxAmount;

            $invoice = HometaxInvoice::create([
                'tenant_id' => $tenantId,
                'nts_confirm_num' => $ntsConfirmNum,
                'invoice_type' => $validated['invoice_type'],
                'write_date' => $validated['write_date'],
                'issue_date' => $validated['write_date'],
                'invoicer_corp_name' => $validated['invoicer_corp_name'] ?? '',
                'invoicer_corp_num' => $validated['invoicer_corp_num'] ?? '',
                'invoicer_ceo_name' => $validated['invoicer_ceo_name'] ?? '',
                'invoicer_branch_num' => $validated['invoicer_branch_num'] ?? '',
                'invoicer_address' => $validated['invoicer_address'] ?? '',
                'invoicer_biz_type' => $validated['invoicer_biz_type'] ?? '',
                'invoicer_biz_class' => $validated['invoicer_biz_class'] ?? '',
                'invoicer_email' => $validated['invoicer_email'] ?? '',
                'invoicer_email2' => $validated['invoicer_email2'] ?? '',
                'invoicee_corp_name' => $validated['invoicee_corp_name'] ?? '',
                'invoicee_corp_num' => $validated['invoicee_corp_num'] ?? '',
                'invoicee_ceo_name' => $validated['invoicee_ceo_name'] ?? '',
                'invoicee_branch_num' => $validated['invoicee_branch_num'] ?? '',
                'invoicee_address' => $validated['invoicee_address'] ?? '',
                'invoicee_biz_type' => $validated['invoicee_biz_type'] ?? '',
                'invoicee_biz_class' => $validated['invoicee_biz_class'] ?? '',
                'invoicee_email' => $validated['invoicee_email'] ?? '',
                'invoicee_email2' => $validated['invoicee_email2'] ?? '',
                'supply_amount' => $validated['supply_amount'],
                'tax_amount' => $taxAmount,
                'total_amount' => $totalAmount,
                'item_name' => $validated['item_name'] ?? '',
                'remark' => $validated['remark'] ?? '',
                'tax_type' => $validated['tax_type'] ?? 1,
                'purpose_type' => $validated['purpose_type'] ?? 1,
                'synced_at' => now(),
            ]);

            return response()->json([
                'success' => true,
                'message' => '수동 세금계산서가 등록되었습니다.',
                'data' => $invoice,
            ]);
        } catch (\Illuminate\Validation\ValidationException $e) {
            return response()->json([
                'success' => false,
                'error' => '입력값 오류: '.implode(', ', $e->validator->errors()->all()),
            ], 422);
        } catch (\Throwable $e) {
            Log::error('수동 세금계산서 저장 오류: '.$e->getMessage());

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

    /**
     * 수동 세금계산서 수정 (MAN- 건만 가능)
     */
    public function manualUpdate(Request $request, int $id): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $invoice = HometaxInvoice::where('id', $id)
                ->where('tenant_id', $tenantId)
                ->firstOrFail();

            if (! str_starts_with($invoice->nts_confirm_num, 'MAN-')) {
                return response()->json([
                    'success' => false,
                    'error' => '수동 입력 건만 수정할 수 있습니다.',
                ], 403);
            }

            $validated = $request->validate([
                'invoice_type' => 'sometimes|in:sales,purchase',
                'write_date' => 'sometimes|date',
                'invoicer_corp_name' => 'nullable|string|max:200',
                'invoicer_corp_num' => 'nullable|string|max:20',
                'invoicer_ceo_name' => 'nullable|string|max:50',
                'invoicer_branch_num' => 'nullable|string|max:10',
                'invoicer_address' => 'nullable|string|max:300',
                'invoicer_biz_type' => 'nullable|string|max:100',
                'invoicer_biz_class' => 'nullable|string|max:100',
                'invoicer_email' => 'nullable|string|max:200',
                'invoicer_email2' => 'nullable|string|max:200',
                'invoicee_corp_name' => 'nullable|string|max:200',
                'invoicee_corp_num' => 'nullable|string|max:20',
                'invoicee_ceo_name' => 'nullable|string|max:50',
                'invoicee_branch_num' => 'nullable|string|max:10',
                'invoicee_address' => 'nullable|string|max:300',
                'invoicee_biz_type' => 'nullable|string|max:100',
                'invoicee_biz_class' => 'nullable|string|max:100',
                'invoicee_email' => 'nullable|string|max:200',
                'invoicee_email2' => 'nullable|string|max:200',
                'supply_amount' => 'sometimes|numeric|min:0',
                'tax_amount' => 'nullable|numeric|min:0',
                'item_name' => 'nullable|string|max:200',
                'remark' => 'nullable|string|max:500',
                'tax_type' => 'nullable|integer|in:1,2,3',
                'purpose_type' => 'nullable|integer|in:1,2',
            ], [
                'invoice_type.in' => '매출/매입 구분값이 올바르지 않습니다.',
                'write_date.date' => '작성일자 형식이 올바르지 않습니다.',
                'supply_amount.numeric' => '공급가액은 숫자여야 합니다.',
                'supply_amount.min' => '공급가액은 0 이상이어야 합니다.',
                'tax_amount.numeric' => '세액은 숫자여야 합니다.',
                'tax_amount.min' => '세액은 0 이상이어야 합니다.',
            ]);

            // nullable 필드 빈 문자열 처리
            foreach (['invoicer_corp_name', 'invoicer_corp_num', 'invoicer_ceo_name', 'invoicer_branch_num', 'invoicer_address', 'invoicer_biz_type', 'invoicer_biz_class', 'invoicer_email', 'invoicer_email2', 'invoicee_corp_name', 'invoicee_corp_num', 'invoicee_ceo_name', 'invoicee_branch_num', 'invoicee_address', 'invoicee_biz_type', 'invoicee_biz_class', 'invoicee_email', 'invoicee_email2'] as $field) {
                if (array_key_exists($field, $validated)) {
                    $validated[$field] = $validated[$field] ?? '';
                }
            }
            $validated['tax_amount'] = $validated['tax_amount'] ?? 0;

            $supply = $validated['supply_amount'] ?? $invoice->supply_amount;
            $tax = $validated['tax_amount'] ?? $invoice->tax_amount;
            $validated['total_amount'] = (float) $supply + (float) $tax;

            $invoice->update($validated);

            return response()->json([
                'success' => true,
                'message' => '수정되었습니다.',
                'data' => $invoice->fresh(),
            ]);
        } 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(),
            ]);
        }
    }

    /**
     * 수동 세금계산서 삭제 (MAN- 건만 가능)
     */
    public function manualDestroy(int $id): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $invoice = HometaxInvoice::where('id', $id)
                ->where('tenant_id', $tenantId)
                ->firstOrFail();

            if (! str_starts_with($invoice->nts_confirm_num, 'MAN-')) {
                return response()->json([
                    'success' => false,
                    'error' => '수동 입력 건만 삭제할 수 있습니다.',
                ], 403);
            }

            $invoice->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(),
            ]);
        }
    }

    /**
     * 세금계산서에서 분개(일반전표) 생성
     */
    public function createJournalEntry(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $validated = $request->validate([
                'invoice_id' => 'required|integer',
                'lines' => 'required|array|min:1',
                'lines.*.dc_type' => 'required|in:debit,credit',
                'lines.*.account_code' => 'required|string',
                'lines.*.account_name' => 'required|string',
                'lines.*.debit_amount' => 'required|numeric|min:0',
                'lines.*.credit_amount' => 'required|numeric|min:0',
                'lines.*.description' => 'nullable|string',
            ]);

            $invoice = HometaxInvoice::where('id', $validated['invoice_id'])
                ->where('tenant_id', $tenantId)
                ->firstOrFail();

            $tradingPartner = $invoice->invoice_type === 'sales'
                ? $invoice->invoicee_corp_name
                : $invoice->invoicer_corp_name;

            DB::transaction(function () use ($tenantId, $invoice, $validated, $tradingPartner) {
                HometaxInvoiceJournal::saveJournals($tenantId, $invoice->id, [
                    'nts_confirm_num' => $invoice->nts_confirm_num,
                    'invoice_type' => $invoice->invoice_type,
                    'write_date' => $invoice->write_date,
                    'supply_amount' => $invoice->supply_amount,
                    'tax_amount' => $invoice->tax_amount,
                    'total_amount' => $invoice->total_amount,
                    'trading_partner_name' => $tradingPartner,
                ], $validated['lines']);
            });

            return response()->json([
                'success' => true,
                'message' => '분개가 저장되었습니다.',
            ]);
        } catch (\Illuminate\Validation\ValidationException $e) {
            $errors = $e->errors();
            $firstError = collect($errors)->flatten()->first() ?? '입력 데이터가 올바르지 않습니다.';
            Log::error('분개 저장 검증 오류', ['errors' => $errors]);

            return response()->json([
                'success' => false,
                'error' => $firstError,
                'errors' => $errors,
            ], 422);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'success' => false,
                'error' => '해당 세금계산서를 찾을 수 없습니다.',
            ], 404);
        } catch (\Throwable $e) {
            Log::error('분개 저장 오류: '.$e->getMessage(), ['trace' => $e->getTraceAsString()]);

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

    /**
     * 특정 인보이스의 분개 조회
     */
    public function getJournals(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $invoiceId = $request->input('invoice_id');

            if (! $invoiceId) {
                return response()->json(['success' => false, 'error' => 'invoice_id는 필수입니다.'], 422);
            }

            $journals = HometaxInvoiceJournal::getByInvoiceId($tenantId, (int) $invoiceId);

            return response()->json([
                'success' => true,
                'data' => $journals->map(function ($j) {
                    return [
                        'dc_type' => $j->dc_type,
                        'account_code' => $j->account_code,
                        'account_name' => $j->account_name,
                        'debit_amount' => $j->debit_amount,
                        'credit_amount' => $j->credit_amount,
                        'description' => $j->description,
                    ];
                }),
            ]);
        } catch (\Throwable $e) {
            Log::error('분개 조회 오류: '.$e->getMessage());

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

    /**
     * 특정 인보이스의 분개 삭제
     */
    public function deleteJournals(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
            $invoiceId = $request->input('invoice_id');

            if (! $invoiceId) {
                return response()->json(['success' => false, 'error' => 'invoice_id는 필수입니다.'], 422);
            }

            $deleted = HometaxInvoiceJournal::deleteJournals($tenantId, (int) $invoiceId);

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

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

    /**
     * 카드내역 조회 (수동입력 참조용)
     */
    public function cardTransactions(Request $request): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);

            $startDate = $request->input('startDate', date('Y-m-d', strtotime('-1 month')));
            $endDate = $request->input('endDate', date('Y-m-d'));
            $search = $request->input('search', '');

            $query = BarobillCardTransaction::where('tenant_id', $tenantId)
                ->whereBetween('use_date', [$startDate, $endDate])
                ->orderByDesc('use_date')
                ->orderByDesc('use_time');

            if (! empty($search)) {
                $query->where(function ($q) use ($search) {
                    $q->where('merchant_name', 'like', "%{$search}%")
                        ->orWhere('merchant_biz_num', 'like', "%{$search}%")
                        ->orWhere('approval_num', 'like', "%{$search}%");
                });
            }

            $transactions = $query->limit(100)->get()->map(function ($t) {
                return [
                    'id' => $t->id,
                    'useDate' => $t->use_date,
                    'useTime' => $t->use_time,
                    'merchantName' => $t->merchant_name,
                    'merchantBizNum' => $t->merchant_biz_num,
                    'approvalNum' => $t->approval_num,
                    'approvalAmount' => (float) $t->approval_amount,
                    'approvalAmountFormatted' => number_format($t->approval_amount),
                    'tax' => (float) ($t->tax ?? 0),
                    'supplyAmount' => (float) ($t->modified_supply_amount ?: ($t->approval_amount - ($t->tax ?? 0))),
                    'cardNum' => $t->card_num ? substr($t->card_num, -4) : '',
                    'cardCompanyName' => $t->card_company_name ?? '',
                ];
            });

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

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