<?php

namespace App\Services;

use App\Models\Tenants\AccountCode;
use App\Models\Tenants\JournalEntry;
use App\Models\Tenants\JournalEntryLine;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class GeneralJournalEntryService extends Service
{
    /**
     * 일반전표입력 통합 목록 (입금 + 출금 + 수기전표)
     * deposits/withdrawals는 계좌이체 건만, LEFT JOIN journal_entries로 분개 여부 표시
     */
    public function index(array $params): array
    {
        $tenantId = $this->tenantId();

        $startDate = $params['start_date'] ?? null;
        $endDate = $params['end_date'] ?? null;
        $search = $params['search'] ?? null;
        $perPage = (int) ($params['per_page'] ?? 20);
        $page = (int) ($params['page'] ?? 1);

        // 1) 입금(transfer) UNION 출금(transfer) UNION 수기전표
        $depositsQuery = DB::table('deposits')
            ->leftJoin('journal_entries', function ($join) use ($tenantId) {
                $join->on('journal_entries.source_key', '=', DB::raw("CONCAT('deposit_', deposits.id)"))
                    ->where('journal_entries.tenant_id', $tenantId)
                    ->where('journal_entries.source_type', JournalEntry::SOURCE_BANK_TRANSACTION)
                    ->whereNull('journal_entries.deleted_at');
            })
            ->where('deposits.tenant_id', $tenantId)
            ->where('deposits.payment_method', 'transfer')
            ->whereNull('deposits.deleted_at')
            ->select([
                'deposits.id',
                'deposits.deposit_date as date',
                DB::raw("'deposit' as division"),
                'deposits.amount',
                'deposits.description',
                DB::raw('COALESCE(journal_entries.description, NULL) as journal_description'),
                'deposits.amount as deposit_amount',
                DB::raw('0 as withdrawal_amount'),
                DB::raw('COALESCE(journal_entries.total_debit, 0) as debit_amount'),
                DB::raw('COALESCE(journal_entries.total_credit, 0) as credit_amount'),
                DB::raw("'linked' as source"),
                'deposits.created_at',
                'deposits.updated_at',
                DB::raw('journal_entries.id as journal_entry_id'),
            ]);

        $withdrawalsQuery = DB::table('withdrawals')
            ->leftJoin('journal_entries', function ($join) use ($tenantId) {
                $join->on('journal_entries.source_key', '=', DB::raw("CONCAT('withdrawal_', withdrawals.id)"))
                    ->where('journal_entries.tenant_id', $tenantId)
                    ->where('journal_entries.source_type', JournalEntry::SOURCE_BANK_TRANSACTION)
                    ->whereNull('journal_entries.deleted_at');
            })
            ->where('withdrawals.tenant_id', $tenantId)
            ->where('withdrawals.payment_method', 'transfer')
            ->whereNull('withdrawals.deleted_at')
            ->select([
                'withdrawals.id',
                'withdrawals.withdrawal_date as date',
                DB::raw("'withdrawal' as division"),
                'withdrawals.amount',
                'withdrawals.description',
                DB::raw('COALESCE(journal_entries.description, NULL) as journal_description'),
                DB::raw('0 as deposit_amount'),
                'withdrawals.amount as withdrawal_amount',
                DB::raw('COALESCE(journal_entries.total_debit, 0) as debit_amount'),
                DB::raw('COALESCE(journal_entries.total_credit, 0) as credit_amount'),
                DB::raw("'linked' as source"),
                'withdrawals.created_at',
                'withdrawals.updated_at',
                DB::raw('journal_entries.id as journal_entry_id'),
            ]);

        $manualQuery = DB::table('journal_entries')
            ->where('journal_entries.tenant_id', $tenantId)
            ->where('journal_entries.source_type', JournalEntry::SOURCE_MANUAL)
            ->whereNull('journal_entries.deleted_at')
            ->select([
                'journal_entries.id',
                'journal_entries.entry_date as date',
                DB::raw("'transfer' as division"),
                'journal_entries.total_debit as amount',
                'journal_entries.description',
                'journal_entries.description as journal_description',
                DB::raw('0 as deposit_amount'),
                DB::raw('0 as withdrawal_amount'),
                'journal_entries.total_debit as debit_amount',
                'journal_entries.total_credit as credit_amount',
                DB::raw("'manual' as source"),
                'journal_entries.created_at',
                'journal_entries.updated_at',
                DB::raw('journal_entries.id as journal_entry_id'),
            ]);

        // 날짜 필터
        if ($startDate) {
            $depositsQuery->where('deposits.deposit_date', '>=', $startDate);
            $withdrawalsQuery->where('withdrawals.withdrawal_date', '>=', $startDate);
            $manualQuery->where('journal_entries.entry_date', '>=', $startDate);
        }
        if ($endDate) {
            $depositsQuery->where('deposits.deposit_date', '<=', $endDate);
            $withdrawalsQuery->where('withdrawals.withdrawal_date', '<=', $endDate);
            $manualQuery->where('journal_entries.entry_date', '<=', $endDate);
        }

        // 검색 필터
        if ($search) {
            $depositsQuery->where(function ($q) use ($search) {
                $q->where('deposits.description', 'like', "%{$search}%")
                    ->orWhere('deposits.client_name', 'like', "%{$search}%");
            });
            $withdrawalsQuery->where(function ($q) use ($search) {
                $q->where('withdrawals.description', 'like', "%{$search}%")
                    ->orWhere('withdrawals.client_name', 'like', "%{$search}%");
            });
            $manualQuery->where('journal_entries.description', 'like', "%{$search}%");
        }

        // UNION
        $unionQuery = $depositsQuery
            ->unionAll($withdrawalsQuery)
            ->unionAll($manualQuery);

        // 전체 건수
        $totalCount = DB::table(DB::raw("({$unionQuery->toSql()}) as union_table"))
            ->mergeBindings($unionQuery)
            ->count();

        // 날짜순 정렬 + 페이지네이션
        $items = DB::table(DB::raw("({$unionQuery->toSql()}) as union_table"))
            ->mergeBindings($unionQuery)
            ->orderBy('date', 'desc')
            ->orderBy('created_at', 'desc')
            ->offset(($page - 1) * $perPage)
            ->limit($perPage)
            ->get();

        // 누적잔액 계산 (해당 기간 전체 기준)
        $allForBalance = DB::table(DB::raw("({$unionQuery->toSql()}) as union_table"))
            ->mergeBindings($unionQuery)
            ->orderBy('date', 'asc')
            ->orderBy('created_at', 'asc')
            ->get(['deposit_amount', 'withdrawal_amount']);

        $runningBalance = 0;
        $balanceMap = [];
        foreach ($allForBalance as $idx => $row) {
            $runningBalance += (int) $row->deposit_amount - (int) $row->withdrawal_amount;
            $balanceMap[$idx] = $runningBalance;
        }

        // 역순이므로 현재 페이지에 해당하는 잔액을 매핑
        $totalItems = count($allForBalance);
        $items = $items->map(function ($item, $index) use ($balanceMap, $totalItems, $page, $perPage) {
            // 역순 인덱스 → 정순 인덱스
            $reverseIdx = $totalItems - 1 - (($page - 1) * $perPage + $index);
            $item->balance = $reverseIdx >= 0 ? ($balanceMap[$reverseIdx] ?? 0) : 0;

            return $item;
        });

        return [
            'data' => $items->toArray(),
            'current_page' => $page,
            'last_page' => (int) ceil($totalCount / $perPage),
            'per_page' => $perPage,
            'total' => $totalCount,
        ];
    }

    /**
     * 요약 통계
     */
    public function summary(array $params): array
    {
        $tenantId = $this->tenantId();

        $startDate = $params['start_date'] ?? null;
        $endDate = $params['end_date'] ?? null;
        $search = $params['search'] ?? null;

        // 입금 통계
        $depositQuery = DB::table('deposits')
            ->where('tenant_id', $tenantId)
            ->where('payment_method', 'transfer')
            ->whereNull('deleted_at');

        // 출금 통계
        $withdrawalQuery = DB::table('withdrawals')
            ->where('tenant_id', $tenantId)
            ->where('payment_method', 'transfer')
            ->whereNull('deleted_at');

        if ($startDate) {
            $depositQuery->where('deposit_date', '>=', $startDate);
            $withdrawalQuery->where('withdrawal_date', '>=', $startDate);
        }
        if ($endDate) {
            $depositQuery->where('deposit_date', '<=', $endDate);
            $withdrawalQuery->where('withdrawal_date', '<=', $endDate);
        }
        if ($search) {
            $depositQuery->where(function ($q) use ($search) {
                $q->where('description', 'like', "%{$search}%")
                    ->orWhere('client_name', 'like', "%{$search}%");
            });
            $withdrawalQuery->where(function ($q) use ($search) {
                $q->where('description', 'like', "%{$search}%")
                    ->orWhere('client_name', 'like', "%{$search}%");
            });
        }

        $depositCount = (clone $depositQuery)->count();
        $depositAmount = (int) (clone $depositQuery)->sum('amount');
        $withdrawalCount = (clone $withdrawalQuery)->count();
        $withdrawalAmount = (int) (clone $withdrawalQuery)->sum('amount');

        // 분개 완료/미완료 건수 (journal_entries가 연결된 입출금 수)
        $journalCompleteCount = DB::table('journal_entries')
            ->where('tenant_id', $tenantId)
            ->where('source_type', JournalEntry::SOURCE_BANK_TRANSACTION)
            ->whereNull('deleted_at')
            ->when($startDate, fn ($q) => $q->where('entry_date', '>=', $startDate))
            ->when($endDate, fn ($q) => $q->where('entry_date', '<=', $endDate))
            ->count();

        $totalCount = $depositCount + $withdrawalCount;
        $journalIncompleteCount = max(0, $totalCount - $journalCompleteCount);

        return [
            'total_count' => $totalCount,
            'deposit_count' => $depositCount,
            'deposit_amount' => $depositAmount,
            'withdrawal_count' => $withdrawalCount,
            'withdrawal_amount' => $withdrawalAmount,
            'journal_complete_count' => $journalCompleteCount,
            'journal_incomplete_count' => $journalIncompleteCount,
        ];
    }

    /**
     * 전표 상세 조회 (분개 수정 모달용)
     */
    public function show(int $id): array
    {
        $tenantId = $this->tenantId();

        $entry = JournalEntry::query()
            ->where('tenant_id', $tenantId)
            ->with('lines')
            ->findOrFail($id);

        // source_type에 따라 원본 거래 정보 조회
        $sourceInfo = $this->getSourceInfo($entry);

        return [
            'id' => $entry->id,
            'date' => $entry->entry_date->format('Y-m-d'),
            'division' => $sourceInfo['division'],
            'amount' => $sourceInfo['amount'],
            'description' => $sourceInfo['description'] ?? $entry->description,
            'bank_name' => $sourceInfo['bank_name'] ?? '',
            'account_number' => $sourceInfo['account_number'] ?? '',
            'journal_memo' => $entry->description,
            'rows' => $entry->lines->map(function ($line) {
                return [
                    'id' => $line->id,
                    'side' => $line->dc_type,
                    'account_subject_id' => $line->account_code,
                    'account_subject_name' => $line->account_name,
                    'vendor_id' => $line->trading_partner_id,
                    'vendor_name' => $line->trading_partner_name ?? '',
                    'debit_amount' => (int) $line->debit_amount,
                    'credit_amount' => (int) $line->credit_amount,
                    'memo' => $line->description ?? '',
                ];
            })->toArray(),
        ];
    }

    /**
     * 수기전표 등록
     */
    public function store(array $data): JournalEntry
    {
        $tenantId = $this->tenantId();

        return DB::transaction(function () use ($data, $tenantId) {
            // 차대 균형 검증
            $this->validateDebitCreditBalance($data['rows']);

            // 전표번호 생성
            $entryNo = $this->generateEntryNo($tenantId, $data['journal_date']);

            // 합계 계산
            $totalDebit = 0;
            $totalCredit = 0;
            foreach ($data['rows'] as $row) {
                $totalDebit += (int) ($row['debit_amount'] ?? 0);
                $totalCredit += (int) ($row['credit_amount'] ?? 0);
            }

            // 전표 생성
            $entry = new JournalEntry;
            $entry->tenant_id = $tenantId;
            $entry->entry_no = $entryNo;
            $entry->entry_date = $data['journal_date'];
            $entry->entry_type = JournalEntry::TYPE_GENERAL;
            $entry->description = $data['description'] ?? null;
            $entry->total_debit = $totalDebit;
            $entry->total_credit = $totalCredit;
            $entry->status = JournalEntry::STATUS_CONFIRMED;
            $entry->source_type = JournalEntry::SOURCE_MANUAL;
            $entry->source_key = null;
            $entry->save();

            // 분개 행 생성
            $this->createLines($entry, $data['rows'], $tenantId);

            return $entry->load('lines');
        });
    }

    /**
     * 분개 수정 (lines 전체 교체)
     */
    public function updateJournal(int $id, array $data): JournalEntry
    {
        $tenantId = $this->tenantId();

        return DB::transaction(function () use ($id, $data, $tenantId) {
            $entry = JournalEntry::query()
                ->where('tenant_id', $tenantId)
                ->findOrFail($id);

            // 메모 업데이트
            if (array_key_exists('journal_memo', $data)) {
                $entry->description = $data['journal_memo'];
            }

            // rows가 있으면 lines 교체
            if (isset($data['rows']) && ! empty($data['rows'])) {
                $this->validateDebitCreditBalance($data['rows']);

                // 기존 lines 삭제
                JournalEntryLine::query()
                    ->where('journal_entry_id', $entry->id)
                    ->delete();

                // 새 lines 생성
                $this->createLines($entry, $data['rows'], $tenantId);

                // 합계 재계산
                $totalDebit = 0;
                $totalCredit = 0;
                foreach ($data['rows'] as $row) {
                    $totalDebit += (int) ($row['debit_amount'] ?? 0);
                    $totalCredit += (int) ($row['credit_amount'] ?? 0);
                }

                $entry->total_debit = $totalDebit;
                $entry->total_credit = $totalCredit;
            }

            $entry->save();

            return $entry->load('lines');
        });
    }

    /**
     * 전표 삭제 (soft delete, lines는 FK CASCADE)
     */
    public function destroyJournal(int $id): bool
    {
        $tenantId = $this->tenantId();

        return DB::transaction(function () use ($id, $tenantId) {
            $entry = JournalEntry::query()
                ->where('tenant_id', $tenantId)
                ->findOrFail($id);

            // lines 먼저 삭제 (soft delete가 아니므로 물리 삭제)
            JournalEntryLine::query()
                ->where('journal_entry_id', $entry->id)
                ->delete();

            $entry->delete(); // soft delete

            return true;
        });
    }

    /**
     * 전표번호 생성: JE-YYYYMMDD-NNN (동시성 안전)
     */
    private function generateEntryNo(int $tenantId, string $date): string
    {
        $dateStr = str_replace('-', '', substr($date, 0, 10));
        $prefix = "JE-{$dateStr}-";

        // SELECT ... FOR UPDATE 락으로 동시성 안전 보장
        $lastEntry = DB::table('journal_entries')
            ->where('tenant_id', $tenantId)
            ->where('entry_no', 'like', "{$prefix}%")
            ->lockForUpdate()
            ->orderBy('entry_no', 'desc')
            ->first(['entry_no']);

        if ($lastEntry) {
            $lastSeq = (int) substr($lastEntry->entry_no, -3);
            $nextSeq = $lastSeq + 1;
        } else {
            $nextSeq = 1;
        }

        return $prefix . str_pad($nextSeq, 3, '0', STR_PAD_LEFT);
    }

    /**
     * 차대 균형 검증
     */
    private function validateDebitCreditBalance(array $rows): void
    {
        $totalDebit = 0;
        $totalCredit = 0;

        foreach ($rows as $row) {
            $totalDebit += (int) ($row['debit_amount'] ?? 0);
            $totalCredit += (int) ($row['credit_amount'] ?? 0);
        }

        if ($totalDebit !== $totalCredit) {
            throw new BadRequestHttpException(__('error.journal_entry.debit_credit_mismatch'));
        }
    }

    /**
     * 분개 행 생성
     */
    private function createLines(JournalEntry $entry, array $rows, int $tenantId): void
    {
        foreach ($rows as $index => $row) {
            $accountCode = $row['account_subject_id'] ?? '';
            $accountName = $this->resolveAccountName($tenantId, $accountCode);
            $vendorName = $this->resolveVendorName($row['vendor_id'] ?? null);

            $line = new JournalEntryLine;
            $line->tenant_id = $tenantId;
            $line->journal_entry_id = $entry->id;
            $line->line_no = $index + 1;
            $line->dc_type = $row['side'];
            $line->account_code = $accountCode;
            $line->account_name = $accountName;
            $line->trading_partner_id = ! empty($row['vendor_id']) ? (int) $row['vendor_id'] : null;
            $line->trading_partner_name = $vendorName;
            $line->debit_amount = (int) ($row['debit_amount'] ?? 0);
            $line->credit_amount = (int) ($row['credit_amount'] ?? 0);
            $line->description = $row['memo'] ?? null;
            $line->save();
        }
    }

    /**
     * 계정과목 코드 → 이름 조회
     */
    private function resolveAccountName(int $tenantId, string $code): string
    {
        if (empty($code)) {
            return '';
        }

        $account = AccountCode::query()
            ->where('tenant_id', $tenantId)
            ->where('code', $code)
            ->first(['name']);

        return $account ? $account->name : $code;
    }

    /**
     * 거래처 ID → 이름 조회
     */
    private function resolveVendorName(?int $vendorId): string
    {
        if (! $vendorId) {
            return '';
        }

        $vendor = DB::table('clients')
            ->where('id', $vendorId)
            ->first(['name']);

        return $vendor ? $vendor->name : '';
    }

    /**
     * 원본 거래 정보 조회 (입금/출금)
     */
    private function getSourceInfo(JournalEntry $entry): array
    {
        if ($entry->source_type === JournalEntry::SOURCE_MANUAL) {
            return [
                'division' => 'transfer',
                'amount' => $entry->total_debit,
                'description' => $entry->description,
                'bank_name' => '',
                'account_number' => '',
            ];
        }

        // bank_transaction → deposit_123 / withdrawal_456
        if ($entry->source_key && str_starts_with($entry->source_key, 'deposit_')) {
            $sourceId = (int) str_replace('deposit_', '', $entry->source_key);
            $deposit = DB::table('deposits')
                ->leftJoin('bank_accounts', 'deposits.bank_account_id', '=', 'bank_accounts.id')
                ->where('deposits.id', $sourceId)
                ->first([
                    'deposits.amount',
                    'deposits.description',
                    'bank_accounts.bank_name',
                    'bank_accounts.account_number',
                ]);

            if ($deposit) {
                return [
                    'division' => 'deposit',
                    'amount' => (int) $deposit->amount,
                    'description' => $deposit->description,
                    'bank_name' => $deposit->bank_name ?? '',
                    'account_number' => $deposit->account_number ?? '',
                ];
            }
        }

        if ($entry->source_key && str_starts_with($entry->source_key, 'withdrawal_')) {
            $sourceId = (int) str_replace('withdrawal_', '', $entry->source_key);
            $withdrawal = DB::table('withdrawals')
                ->leftJoin('bank_accounts', 'withdrawals.bank_account_id', '=', 'bank_accounts.id')
                ->where('withdrawals.id', $sourceId)
                ->first([
                    'withdrawals.amount',
                    'withdrawals.description',
                    'bank_accounts.bank_name',
                    'bank_accounts.account_number',
                ]);

            if ($withdrawal) {
                return [
                    'division' => 'withdrawal',
                    'amount' => (int) $withdrawal->amount,
                    'description' => $withdrawal->description,
                    'bank_name' => $withdrawal->bank_name ?? '',
                    'account_number' => $withdrawal->account_number ?? '',
                ];
            }
        }

        return [
            'division' => 'transfer',
            'amount' => $entry->total_debit,
            'description' => $entry->description,
            'bank_name' => '',
            'account_number' => '',
        ];
    }
}
