<?php

namespace App\Services\HR;

use App\Models\HR\Employee;
use App\Models\HR\Payroll;
use App\Models\HR\PayrollSetting;
use App\Models\Tenants\Department;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;

class PayrollService
{
    // =========================================================================
    // 간이세액표 (국세청 근로소득 간이세액표 기준, 부양가족 1인)
    // 월 급여액 구간별 세액 (원)
    // =========================================================================

    private const INCOME_TAX_TABLE = [
        // [하한, 상한, 세액]
        [0, 1060000, 0],
        [1060000, 1500000, 19060],
        [1500000, 2000000, 34680],
        [2000000, 2500000, 60430],
        [2500000, 3000000, 95960],
        [3000000, 3500000, 137900],
        [3500000, 4000000, 179000],
        [4000000, 4500000, 215060],
        [4500000, 5000000, 258190],
        [5000000, 6000000, 311810],
        [6000000, 7000000, 414810],
        [7000000, 8000000, 519370],
        [8000000, 9000000, 633640],
        [9000000, 10000000, 758640],
        [10000000, 14000000, 974780],
        [14000000, 28000000, 1801980],
        [28000000, 30000000, 5765680],
        [30000000, 45000000, 6387680],
        [45000000, 87000000, 11462680],
        [87000000, PHP_INT_MAX, 29082680],
    ];

    /**
     * 필터 적용 쿼리 생성 (목록/엑셀 공통)
     */
    private function buildFilteredQuery(array $filters = [])
    {
        $tenantId = session('selected_tenant_id', 1);

        $query = Payroll::query()
            ->with(['user', 'user.tenantProfiles' => fn ($q) => $q->where('tenant_id', $tenantId), 'user.tenantProfiles.department'])
            ->forTenant($tenantId);

        if (! empty($filters['q'])) {
            $search = $filters['q'];
            $query->whereHas('user', function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%");
            });
        }

        if (! empty($filters['department_id'])) {
            $deptId = $filters['department_id'];
            $query->whereHas('user.tenantProfiles', function ($q) use ($tenantId, $deptId) {
                $q->where('tenant_id', $tenantId)->where('department_id', $deptId);
            });
        }

        if (! empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        $year = $filters['year'] ?? now()->year;
        $month = $filters['month'] ?? now()->month;
        $query->forPeriod((int) $year, (int) $month);

        return $query->orderBy('created_at', 'desc');
    }

    /**
     * 급여 목록 조회 (페이지네이션)
     */
    public function getPayrolls(array $filters = [], int $perPage = 20): LengthAwarePaginator
    {
        return $this->buildFilteredQuery($filters)->paginate($perPage);
    }

    /**
     * 엑셀 내보내기용 데이터 (전체)
     */
    public function getExportData(array $filters = []): Collection
    {
        return $this->buildFilteredQuery($filters)->get();
    }

    /**
     * 월간 통계 (통계 카드용)
     */
    public function getMonthlyStats(?int $year = null, ?int $month = null): array
    {
        $tenantId = session('selected_tenant_id', 1);
        $year = $year ?? now()->year;
        $month = $month ?? now()->month;

        $result = Payroll::query()
            ->forTenant($tenantId)
            ->forPeriod($year, $month)
            ->select(
                DB::raw('COUNT(*) as total_count'),
                DB::raw('SUM(gross_salary) as total_gross'),
                DB::raw('SUM(total_deductions) as total_deductions'),
                DB::raw('SUM(net_salary) as total_net'),
                DB::raw("SUM(CASE WHEN status = 'draft' THEN 1 ELSE 0 END) as draft_count"),
                DB::raw("SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count"),
                DB::raw("SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) as paid_count"),
            )
            ->first();

        return [
            'total_gross' => (int) ($result->total_gross ?? 0),
            'total_deductions' => (int) ($result->total_deductions ?? 0),
            'total_net' => (int) ($result->total_net ?? 0),
            'total_count' => (int) ($result->total_count ?? 0),
            'draft_count' => (int) ($result->draft_count ?? 0),
            'confirmed_count' => (int) ($result->confirmed_count ?? 0),
            'paid_count' => (int) ($result->paid_count ?? 0),
            'year' => $year,
            'month' => $month,
        ];
    }

    /**
     * 급여 등록
     */
    public function storePayroll(array $data): Payroll
    {
        $tenantId = session('selected_tenant_id', 1);

        // 동일 대상/기간 중복 체크 (soft-deleted 포함 — DB 유니크 제약과 일치)
        $existing = Payroll::withTrashed()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $data['user_id'])
            ->where('pay_year', $data['pay_year'])
            ->where('pay_month', $data['pay_month'])
            ->first();

        if ($existing) {
            if ($existing->trashed()) {
                $existing->forceDelete();
            } else {
                throw new \InvalidArgumentException(
                    "해당 직원의 {$data['pay_year']}년 {$data['pay_month']}월 급여가 이미 등록되어 있습니다."
                );
            }
        }

        return DB::transaction(function () use ($data, $tenantId) {
            $calculated = $this->calculateAmounts($data);

            $payroll = Payroll::create([
                'tenant_id' => $tenantId,
                'user_id' => $data['user_id'],
                'pay_year' => $data['pay_year'],
                'pay_month' => $data['pay_month'],
                'base_salary' => $data['base_salary'] ?? 0,
                'overtime_pay' => $data['overtime_pay'] ?? 0,
                'bonus' => $data['bonus'] ?? 0,
                'allowances' => $data['allowances'] ?? null,
                'gross_salary' => $calculated['gross_salary'],
                'income_tax' => $calculated['income_tax'],
                'resident_tax' => $calculated['resident_tax'],
                'health_insurance' => $calculated['health_insurance'],
                'pension' => $calculated['pension'],
                'employment_insurance' => $calculated['employment_insurance'],
                'deductions' => $data['deductions'] ?? null,
                'total_deductions' => $calculated['total_deductions'],
                'net_salary' => $calculated['net_salary'],
                'status' => 'draft',
                'note' => $data['note'] ?? null,
                'created_by' => auth()->id(),
                'updated_by' => auth()->id(),
            ]);

            return $payroll->load('user');
        });
    }

    /**
     * 급여 수정
     */
    public function updatePayroll(int $id, array $data): ?Payroll
    {
        $tenantId = session('selected_tenant_id', 1);

        $payroll = Payroll::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $payroll || ! $payroll->isEditable()) {
            return null;
        }

        $mergedData = array_merge($payroll->toArray(), $data);
        $calculated = $this->calculateAmounts($mergedData);

        $payroll->update([
            'base_salary' => $data['base_salary'] ?? $payroll->base_salary,
            'overtime_pay' => $data['overtime_pay'] ?? $payroll->overtime_pay,
            'bonus' => $data['bonus'] ?? $payroll->bonus,
            'allowances' => array_key_exists('allowances', $data) ? $data['allowances'] : $payroll->allowances,
            'gross_salary' => $calculated['gross_salary'],
            'income_tax' => $calculated['income_tax'],
            'resident_tax' => $calculated['resident_tax'],
            'health_insurance' => $calculated['health_insurance'],
            'pension' => $calculated['pension'],
            'employment_insurance' => $calculated['employment_insurance'],
            'deductions' => array_key_exists('deductions', $data) ? $data['deductions'] : $payroll->deductions,
            'total_deductions' => $calculated['total_deductions'],
            'net_salary' => $calculated['net_salary'],
            'note' => $data['note'] ?? $payroll->note,
            'updated_by' => auth()->id(),
        ]);

        return $payroll->fresh(['user']);
    }

    /**
     * 급여 삭제
     */
    public function deletePayroll(int $id): bool
    {
        $tenantId = session('selected_tenant_id', 1);

        $payroll = Payroll::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $payroll || ! $payroll->isDeletable()) {
            return false;
        }

        $payroll->update(['deleted_by' => auth()->id()]);
        $payroll->delete();

        return true;
    }

    /**
     * 급여 확정
     */
    public function confirmPayroll(int $id): ?Payroll
    {
        $tenantId = session('selected_tenant_id', 1);

        $payroll = Payroll::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $payroll || ! $payroll->isConfirmable()) {
            return null;
        }

        $payroll->update([
            'status' => Payroll::STATUS_CONFIRMED,
            'confirmed_at' => now(),
            'confirmed_by' => auth()->id(),
            'updated_by' => auth()->id(),
        ]);

        return $payroll->fresh(['user']);
    }

    /**
     * 급여 지급 처리
     */
    public function payPayroll(int $id): ?Payroll
    {
        $tenantId = session('selected_tenant_id', 1);

        $payroll = Payroll::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $payroll || ! $payroll->isPayable()) {
            return null;
        }

        $payroll->update([
            'status' => Payroll::STATUS_PAID,
            'paid_at' => now(),
            'updated_by' => auth()->id(),
        ]);

        return $payroll->fresh(['user']);
    }

    /**
     * 일괄 생성 (재직 사원 전체)
     */
    public function bulkGenerate(int $year, int $month): array
    {
        $tenantId = session('selected_tenant_id', 1);
        $settings = PayrollSetting::getOrCreate($tenantId);
        $created = 0;
        $skipped = 0;

        $employees = Employee::query()
            ->with('user:id,name')
            ->forTenant($tenantId)
            ->activeEmployees()
            ->get();

        DB::transaction(function () use ($employees, $tenantId, $year, $month, $settings, &$created, &$skipped) {
            foreach ($employees as $employee) {
                $exists = Payroll::query()
                    ->where('tenant_id', $tenantId)
                    ->where('user_id', $employee->user_id)
                    ->forPeriod($year, $month)
                    ->exists();

                if ($exists) {
                    $skipped++;

                    continue;
                }

                $annualSalary = $employee->getJsonExtraValue('salary', 0);
                $baseSalary = $annualSalary > 0 ? round($annualSalary / 12) : 0;

                $data = [
                    'base_salary' => $baseSalary,
                    'overtime_pay' => 0,
                    'bonus' => 0,
                    'allowances' => null,
                    'deductions' => null,
                ];

                $calculated = $this->calculateAmounts($data, $settings);

                Payroll::create([
                    'tenant_id' => $tenantId,
                    'user_id' => $employee->user_id,
                    'pay_year' => $year,
                    'pay_month' => $month,
                    'base_salary' => $baseSalary,
                    'overtime_pay' => 0,
                    'bonus' => 0,
                    'allowances' => null,
                    'gross_salary' => $calculated['gross_salary'],
                    'income_tax' => $calculated['income_tax'],
                    'resident_tax' => $calculated['resident_tax'],
                    'health_insurance' => $calculated['health_insurance'],
                    'pension' => $calculated['pension'],
                    'employment_insurance' => $calculated['employment_insurance'],
                    'deductions' => null,
                    'total_deductions' => $calculated['total_deductions'],
                    'net_salary' => $calculated['net_salary'],
                    'status' => 'draft',
                    'created_by' => auth()->id(),
                    'updated_by' => auth()->id(),
                ]);

                $created++;
            }
        });

        return ['created' => $created, 'skipped' => $skipped];
    }

    /**
     * 급여 금액 자동 계산
     */
    public function calculateAmounts(array $data, ?PayrollSetting $settings = null): array
    {
        $settings = $settings ?? PayrollSetting::getOrCreate();

        $baseSalary = (float) ($data['base_salary'] ?? 0);
        $overtimePay = (float) ($data['overtime_pay'] ?? 0);
        $bonus = (float) ($data['bonus'] ?? 0);

        // 수당 합계
        $allowancesTotal = 0;
        if (! empty($data['allowances'])) {
            $allowances = is_string($data['allowances']) ? json_decode($data['allowances'], true) : $data['allowances'];
            foreach ($allowances ?? [] as $allowance) {
                $allowancesTotal += (float) ($allowance['amount'] ?? 0);
            }
        }

        // 총 지급액
        $grossSalary = $baseSalary + $overtimePay + $bonus + $allowancesTotal;

        // 4대보험 계산
        $healthInsurance = $this->calculateHealthInsurance($grossSalary, $settings);
        $pension = $this->calculatePension($grossSalary, $settings);
        $employmentInsurance = $this->calculateEmploymentInsurance($grossSalary, $settings);

        // 근로소득세 (간이세액표)
        $incomeTax = $this->calculateIncomeTax($grossSalary);
        // 지방소득세 (근로소득세의 10%)
        $residentTax = (int) floor($incomeTax * ($settings->resident_tax_rate / 100));

        // 추가 공제 합계
        $extraDeductions = 0;
        if (! empty($data['deductions'])) {
            $deductions = is_string($data['deductions']) ? json_decode($data['deductions'], true) : $data['deductions'];
            foreach ($deductions ?? [] as $deduction) {
                $extraDeductions += (float) ($deduction['amount'] ?? 0);
            }
        }

        // 총 공제액
        $totalDeductions = $incomeTax + $residentTax + $healthInsurance + $pension + $employmentInsurance + $extraDeductions;

        // 실수령액
        $netSalary = $grossSalary - $totalDeductions;

        return [
            'gross_salary' => (int) $grossSalary,
            'income_tax' => $incomeTax,
            'resident_tax' => $residentTax,
            'health_insurance' => $healthInsurance,
            'pension' => $pension,
            'employment_insurance' => $employmentInsurance,
            'total_deductions' => (int) $totalDeductions,
            'net_salary' => (int) max(0, $netSalary),
        ];
    }

    /**
     * 근로소득세 계산 (간이세액표 기준, 부양가족 1인)
     */
    public function calculateIncomeTax(float $grossSalary, int $dependents = 1): int
    {
        if ($grossSalary <= 0) {
            return 0;
        }

        $tax = 0;
        foreach (self::INCOME_TAX_TABLE as [$min, $max, $amount]) {
            if ($grossSalary > $min && $grossSalary <= $max) {
                $tax = $amount;
                break;
            }
        }

        // 마지막 구간 초과
        if ($grossSalary > 87000000) {
            $tax = self::INCOME_TAX_TABLE[count(self::INCOME_TAX_TABLE) - 1][2];
        }

        return (int) $tax;
    }

    /**
     * 건강보험료 계산
     */
    private function calculateHealthInsurance(float $grossSalary, PayrollSetting $settings): int
    {
        $healthInsurance = $grossSalary * ($settings->health_insurance_rate / 100);
        $longTermCare = $healthInsurance * ($settings->long_term_care_rate / 100);

        return (int) round($healthInsurance + $longTermCare);
    }

    /**
     * 국민연금 계산
     */
    private function calculatePension(float $grossSalary, PayrollSetting $settings): int
    {
        $base = min(max($grossSalary, (float) $settings->pension_min_salary), (float) $settings->pension_max_salary);

        return (int) round($base * ($settings->pension_rate / 100));
    }

    /**
     * 고용보험료 계산
     */
    private function calculateEmploymentInsurance(float $grossSalary, PayrollSetting $settings): int
    {
        return (int) round($grossSalary * ($settings->employment_insurance_rate / 100));
    }

    /**
     * 부서 목록 (드롭다운용)
     */
    public function getDepartments(): Collection
    {
        $tenantId = session('selected_tenant_id', 1);

        return Department::query()
            ->where('is_active', true)
            ->when($tenantId, fn ($q) => $q->where('tenant_id', $tenantId))
            ->orderBy('sort_order')
            ->orderBy('name')
            ->get(['id', 'name', 'code']);
    }

    /**
     * 활성 사원 목록 (드롭다운용)
     */
    public function getActiveEmployees(): Collection
    {
        $tenantId = session('selected_tenant_id', 1);

        return Employee::query()
            ->with('user:id,name')
            ->forTenant($tenantId)
            ->activeEmployees()
            ->orderBy('display_name')
            ->get(['id', 'user_id', 'display_name', 'department_id', 'json_extra']);
    }

    /**
     * 급여 설정 조회/생성
     */
    public function getSettings(): PayrollSetting
    {
        return PayrollSetting::getOrCreate();
    }

    /**
     * 급여 설정 수정
     */
    public function updateSettings(array $data): PayrollSetting
    {
        $settings = PayrollSetting::getOrCreate();

        $settings->update([
            'health_insurance_rate' => $data['health_insurance_rate'] ?? $settings->health_insurance_rate,
            'long_term_care_rate' => $data['long_term_care_rate'] ?? $settings->long_term_care_rate,
            'pension_rate' => $data['pension_rate'] ?? $settings->pension_rate,
            'employment_insurance_rate' => $data['employment_insurance_rate'] ?? $settings->employment_insurance_rate,
            'pension_max_salary' => $data['pension_max_salary'] ?? $settings->pension_max_salary,
            'pension_min_salary' => $data['pension_min_salary'] ?? $settings->pension_min_salary,
            'pay_day' => $data['pay_day'] ?? $settings->pay_day,
            'auto_calculate' => $data['auto_calculate'] ?? $settings->auto_calculate,
        ]);

        return $settings->fresh();
    }
}
