<?php

namespace App\Services\Payroll;

use App\Models\CompanySetting;
use App\Models\DeductionSection;
use App\Models\EmployeeTdsRecord;
use App\Models\IncomeTaxDeclaration;
use App\Models\IncomeTaxDeclarationItem;
use App\Models\IncomeTaxPlan;
use App\Models\User;
use App\Notifications\IncomeTaxDeclarationReviewedNotification;
use App\Notifications\IncomeTaxDeclarationSubmittedNotification;
use App\Services\HrmsNotificationService;
use App\Support\FinancialYear;
use Carbon\Carbon;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class IncomeTaxDeclarationService
{
    public const DEDUCTION_SECTIONS = [
        '80C' => ['name' => 'Section 80C (PPF, ELSS, LIC, etc.)', 'limit' => 150000, 'proof_required' => true],
        '80D' => ['name' => 'Section 80D (Health Insurance)', 'limit' => 25000, 'proof_required' => true],
        '80CCD1B' => ['name' => 'Section 80CCD(1B) — NPS additional', 'limit' => 50000, 'proof_required' => true],
        '80E' => ['name' => 'Section 80E (Education Loan Interest)', 'limit' => null, 'proof_required' => true],
        'HRA' => ['name' => 'House Rent Allowance (HRA)', 'limit' => null, 'proof_required' => true],
        'LTA' => ['name' => 'Leave Travel Allowance (LTA)', 'limit' => null, 'proof_required' => true],
        'OTHER' => ['name' => 'Other Chapter VI-A deductions', 'limit' => null, 'proof_required' => true],
    ];

    /** @var array<int, array<string, string>> */
    protected array $settingsCache = [];

    /** @var array<string, array<string, array{name: string, limit: ?float, proof_required: bool}>> */
    protected array $sectionsCache = [];

    public function __construct(
        protected TdsCalculator $calculator
    ) {}

    /**
     * @return array<string, array{name: string, limit: ?float, proof_required: bool}>
     */
    public function sectionsForRegime(string $regime): array
    {
        if ($regime === 'new') {
            return [];
        }

        if (isset($this->sectionsCache[$regime])) {
            return $this->sectionsCache[$regime];
        }

        $fromDb = DeductionSection::query()
            ->where('is_active', true)
            ->orderBy('section_code')
            ->get();

        if ($fromDb->isNotEmpty()) {
            $sections = [];
            foreach ($fromDb as $section) {
                $sections[$section->section_code] = [
                    'name' => $section->section_name,
                    'limit' => $section->limit_amount !== null ? (float) $section->limit_amount : null,
                    'proof_required' => (bool) ($section->proof_required ?? true),
                ];
            }

            return $this->sectionsCache[$regime] = $sections;
        }

        return $this->sectionsCache[$regime] = self::DEDUCTION_SECTIONS;
    }

    public function isProofMandatory(int $companyId): bool
    {
        $settings = $this->companyPayrollSettings($companyId);

        return ($settings['proof_mandatory'] ?? '0') === '1';
    }

    public function findDeclaration(int $companyId, int $userId, ?string $financialYear = null): ?IncomeTaxDeclaration
    {
        $financialYear = $financialYear ?? FinancialYear::forDate();

        return IncomeTaxDeclaration::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $financialYear)
            ->with('items')
            ->first();
    }

    /**
     * Live TDS estimate without persisting.
     *
     * @param  array<string, float|string>  $amounts
     * @return array{taxable_income: float, annual_tax: float, monthly_tds: float, deductions: float, gross_annual: float}
     */
    public function previewProjection(int $companyId, int $userId, string $regime, array $amounts, ?string $financialYear = null): array
    {
        $financialYear = $financialYear ?? FinancialYear::forDate();
        $user = User::withoutGlobalScopes()->find($userId);
        $gross = (float) ($user?->latestEffectiveSalaryRevision()?->ctc_annual ?? 0);

        $declaration = new IncomeTaxDeclaration([
            'company_id' => $companyId,
            'user_id' => $userId,
            'financial_year' => $financialYear,
            'regime' => $regime,
            'gross_annual_estimate' => $gross,
            'status' => IncomeTaxDeclaration::STATUS_DRAFT,
        ]);

        $plan = $this->resolvePlan($companyId, $regime);
        if ($plan) {
            $declaration->income_tax_plan_id = $plan->id;
            $declaration->setRelation('plan', $plan);
        }

        $items = collect();
        if ($regime === 'old') {
            foreach ($this->sectionsForRegime('old') as $code => $meta) {
                $amount = max(0, (float) ($amounts[$code] ?? 0));
                $limit = $meta['limit'] ?? null;
                if ($limit !== null) {
                    $amount = min($amount, (float) $limit);
                }
                if ($amount <= 0) {
                    continue;
                }
                $items->push(new IncomeTaxDeclarationItem([
                    'section_code' => $code,
                    'section_name' => $meta['name'],
                    'declared_amount' => $amount,
                ]));
            }
        }

        $declaration->setRelation('items', $items);

        $projection = $this->projectTax($declaration);
        $projection['gross_annual'] = $gross;

        return $projection;
    }

    /**
     * @return list<array{code: string, name: string}>
     */
    public function missingProofSections(IncomeTaxDeclaration $declaration): array
    {
        if ($declaration->regime !== 'old' || ! $this->isProofMandatory((int) $declaration->company_id)) {
            return [];
        }

        $sections = $this->sectionsForRegime('old');
        $missing = [];

        foreach ($declaration->items as $item) {
            if ((float) $item->declared_amount <= 0) {
                continue;
            }

            $proofRequired = $sections[$item->section_code]['proof_required'] ?? true;
            if ($proofRequired && ! $item->proof_path) {
                $missing[] = [
                    'code' => $item->section_code,
                    'name' => $item->section_name,
                ];
            }
        }

        return $missing;
    }

    public function bulkReviewProofs(IncomeTaxDeclaration $declaration, string $status): int
    {
        $count = 0;
        foreach ($declaration->items as $item) {
            if ((float) $item->declared_amount <= 0) {
                continue;
            }
            $this->reviewItem($item, $status, null, null);
            $count++;
        }

        return $count;
    }

    public function assertReadyForFinalVerify(IncomeTaxDeclaration $declaration): void
    {
        $declaration->loadMissing('items');

        if ($declaration->items->isEmpty()) {
            return;
        }

        $unapproved = $declaration->unapprovedProofItems();
        if ($unapproved->isEmpty()) {
            return;
        }

        $labels = $unapproved
            ->map(fn (IncomeTaxDeclarationItem $item) => $item->section_code . ' (' . ucfirst($item->proof_status) . ')')
            ->implode(', ');

        throw new \RuntimeException(
            'All deduction proof statuses must be Approved before final verification. Pending: ' . $labels
        );
    }

    public function declarationWindowOpen(int $companyId): bool
    {
        $settings = $this->companyPayrollSettings($companyId);

        if (($settings['payroll_declaration_window_enabled'] ?? '0') !== '1') {
            return true;
        }

        $today = now();

        $yearFrom = $settings['declaration_year_from'] ?? null;
        $yearTo = $settings['declaration_year_to'] ?? null;

        if ($yearFrom && $yearTo) {
            $from = Carbon::parse($yearFrom)->startOfDay();
            $to = Carbon::parse($yearTo)->endOfDay();

            return $today->betweenIncluded($from, $to);
        }

        $fromDay = max(1, (int) ($settings['declaration_from_day'] ?? 1));
        $toDay = max($fromDay, (int) ($settings['declaration_to_day'] ?? 25));

        return $today->day >= $fromDay && $today->day <= $toDay;
    }

    public function getOrCreateDeclaration(int $companyId, int $userId, ?string $financialYear = null): IncomeTaxDeclaration
    {
        $financialYear = $financialYear ?? FinancialYear::forDate();

        $declaration = IncomeTaxDeclaration::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $financialYear)
            ->first();

        if ($declaration) {
            return $declaration;
        }

        $user = User::withoutGlobalScopes()->findOrFail($userId);
        $gross = (float) ($user->latestEffectiveSalaryRevision()?->ctc_annual ?? 0);
        $regime = 'new';
        $plan = $this->resolvePlan($companyId, $regime);

        return IncomeTaxDeclaration::withoutGlobalScopes()->create([
            'company_id' => $companyId,
            'user_id' => $userId,
            'financial_year' => $financialYear,
            'regime' => $regime,
            'income_tax_plan_id' => $plan?->id,
            'gross_annual_estimate' => $gross,
            'status' => IncomeTaxDeclaration::STATUS_DRAFT,
        ]);
    }

    /**
     * @param  array<string, float>  $amounts
     */
    public function saveDeclaration(IncomeTaxDeclaration $declaration, string $regime, array $amounts): IncomeTaxDeclaration
    {
        if (! $declaration->isEditable()) {
            throw new \RuntimeException('Declaration cannot be edited in its current status.');
        }

        return DB::transaction(function () use ($declaration, $regime, $amounts) {
            $plan = $this->resolvePlan((int) $declaration->company_id, $regime);
            $existingItems = $declaration->items()->get()->keyBy('section_code');

            $declaration->update([
                'regime' => $regime,
                'income_tax_plan_id' => $plan?->id,
            ]);

            if ($regime === 'new') {
                foreach ($existingItems as $item) {
                    if ($item->proof_path) {
                        Storage::disk('local')->delete($item->proof_path);
                    }
                    $item->delete();
                }
                $totalDeclared = 0.0;
            } else {
                $sections = $this->sectionsForRegime($regime);
                $totalDeclared = 0.0;
                $activeCodes = [];

                foreach ($sections as $code => $meta) {
                    $amount = max(0, (float) ($amounts[$code] ?? 0));
                    $limit = $meta['limit'] ?? null;

                    if ($limit !== null) {
                        $amount = min($amount, (float) $limit);
                    }

                    if ($amount <= 0) {
                        if ($existing = $existingItems->get($code)) {
                            if ($existing->proof_path) {
                                Storage::disk('local')->delete($existing->proof_path);
                            }
                            $existing->delete();
                        }

                        continue;
                    }

                    $activeCodes[] = $code;
                    $totalDeclared += $amount;

                    if ($existing = $existingItems->get($code)) {
                        $updates = [
                            'section_name' => $meta['name'],
                            'declared_amount' => $amount,
                            'section_limit' => $limit,
                        ];

                        if ((float) $existing->declared_amount !== $amount) {
                            if ($existing->proof_path) {
                                Storage::disk('local')->delete($existing->proof_path);
                            }
                            $updates['proof_path'] = null;
                            $updates['proof_status'] = IncomeTaxDeclarationItem::PROOF_PENDING;
                            $updates['verified_amount'] = null;
                            $updates['review_remarks'] = null;
                        }

                        $existing->update($updates);
                    } else {
                        IncomeTaxDeclarationItem::create([
                            'income_tax_declaration_id' => $declaration->id,
                            'section_code' => $code,
                            'section_name' => $meta['name'],
                            'declared_amount' => $amount,
                            'section_limit' => $limit,
                            'proof_status' => IncomeTaxDeclarationItem::PROOF_PENDING,
                        ]);
                    }
                }

                foreach ($existingItems as $code => $orphan) {
                    if (! in_array($code, $activeCodes, true)) {
                        if ($orphan->proof_path) {
                            Storage::disk('local')->delete($orphan->proof_path);
                        }
                        $orphan->delete();
                    }
                }
            }

            $projection = $this->projectTax($declaration->fresh(['items']));

            $declaration->update([
                'total_deductions_declared' => $totalDeclared,
                'taxable_income' => $projection['taxable_income'],
                'annual_tax' => $projection['annual_tax'],
                'monthly_tds' => $projection['monthly_tds'],
            ]);

            return $declaration->fresh(['items', 'user', 'plan']);
        });
    }

    public function submit(IncomeTaxDeclaration $declaration): IncomeTaxDeclaration
    {
        if (! $declaration->isEditable()) {
            throw new \RuntimeException('Declaration cannot be submitted.');
        }

        if (! $this->declarationWindowOpen((int) $declaration->company_id)) {
            throw new \RuntimeException('Declaration window is closed.');
        }

        $declaration->loadMissing('items');
        $missingProofs = $this->missingProofSections($declaration);
        if ($missingProofs !== []) {
            $labels = implode(', ', array_column($missingProofs, 'name'));
            throw new \RuntimeException('Upload proof documents before submitting: ' . $labels);
        }

        $declaration->update([
            'status' => IncomeTaxDeclaration::STATUS_SUBMITTED,
            'submitted_at' => now(),
        ]);

        $declaration = $declaration->fresh(['items', 'user']);
        app(HrmsNotificationService::class)->notifyIncomeTaxVerifiers(
            (int) $declaration->company_id,
            new IncomeTaxDeclarationSubmittedNotification($declaration),
            [(int) $declaration->user_id]
        );

        return $declaration;
    }

    public function verifyDeclaration(IncomeTaxDeclaration $declaration, int $verifierId, ?string $remarks = null): IncomeTaxDeclaration
    {
        $declaration->loadMissing('items');
        $this->assertReadyForFinalVerify($declaration);

        return DB::transaction(function () use ($declaration, $verifierId, $remarks) {
            $verifiedTotal = 0.0;

            foreach ($declaration->items as $item) {
                $amount = $item->verified_amount ?? $item->declared_amount;
                $verifiedTotal += (float) $amount;
            }

            $declaration->update([
                'status' => IncomeTaxDeclaration::STATUS_VERIFIED,
                'total_deductions_verified' => $verifiedTotal,
                'verified_at' => now(),
                'verified_by' => $verifierId,
                'hr_remarks' => $remarks,
            ]);

            $projection = $this->projectTax($declaration->fresh(['items']), useVerified: true);

            $declaration->update([
                'taxable_income' => $projection['taxable_income'],
                'annual_tax' => $projection['annual_tax'],
                'monthly_tds' => $projection['monthly_tds'],
            ]);

            $declaration = $declaration->fresh(['items', 'user', 'verifier']);
            $declaration->user?->notify(new IncomeTaxDeclarationReviewedNotification($declaration, 'verified'));

            return $declaration;
        });
    }

    public function rejectDeclaration(IncomeTaxDeclaration $declaration, int $verifierId, string $remarks): IncomeTaxDeclaration
    {
        $declaration->update([
            'status' => IncomeTaxDeclaration::STATUS_REJECTED,
            'verified_by' => $verifierId,
            'verified_at' => now(),
            'hr_remarks' => $remarks,
        ]);

        $declaration = $declaration->fresh(['items', 'user']);
        $declaration->user?->notify(new IncomeTaxDeclarationReviewedNotification($declaration, 'rejected'));

        return $declaration;
    }

    public function reviewItem(IncomeTaxDeclarationItem $item, string $status, ?float $verifiedAmount = null, ?string $remarks = null): IncomeTaxDeclarationItem
    {
        $item->update([
            'proof_status' => $status,
            'verified_amount' => $verifiedAmount ?? $item->declared_amount,
            'review_remarks' => $remarks,
        ]);

        return $item->fresh();
    }

    public function uploadProof(IncomeTaxDeclarationItem $item, UploadedFile $file): IncomeTaxDeclarationItem
    {
        $declaration = $item->declaration;
        $path = $file->store(
            'income-tax-proofs/' . $declaration->company_id . '/' . $declaration->user_id,
            'local'
        );

        if ($item->proof_path) {
            Storage::disk('local')->delete($item->proof_path);
        }

        $item->update([
            'proof_path' => $path,
            'proof_status' => IncomeTaxDeclarationItem::PROOF_PENDING,
        ]);

        return $item->fresh();
    }

    /**
     * @return array{taxable_income: float, annual_tax: float, monthly_tds: float, deductions: float}
     */
    public function projectTax(IncomeTaxDeclaration $declaration, bool $useVerified = false): array
    {
        $plan = $declaration->plan ?? $this->resolvePlan((int) $declaration->company_id, $declaration->regime);

        if (! $plan) {
            return [
                'taxable_income' => (float) $declaration->gross_annual_estimate,
                'annual_tax' => 0.0,
                'monthly_tds' => 0.0,
                'deductions' => 0.0,
            ];
        }

        $deductions = 0.0;

        if ($declaration->regime === 'old') {
            foreach ($declaration->items as $item) {
                if ($useVerified && $declaration->status === IncomeTaxDeclaration::STATUS_VERIFIED) {
                    $deductions += (float) ($item->verified_amount ?? $item->declared_amount);
                } else {
                    $deductions += (float) $item->declared_amount;
                }
            }
        }

        $gross = (float) $declaration->gross_annual_estimate;
        $result = $this->calculator->compute($plan, $gross, $deductions);

        $alreadyDeducted = $this->cumulativeTdsForYear(
            (int) $declaration->company_id,
            (int) $declaration->user_id,
            $declaration->financial_year
        );

        $remainingTax = max(0, $result['annual_tax'] - $alreadyDeducted);
        $remainingMonths = FinancialYear::remainingMonths();
        $monthlyTds = round($remainingTax / $remainingMonths, 2);

        return [
            'taxable_income' => $result['taxable_income'],
            'annual_tax' => $result['annual_tax'],
            'monthly_tds' => $monthlyTds,
            'deductions' => $deductions,
        ];
    }

    public function monthlyTdsForPayroll(int $companyId, int $userId, int $month, int $year): float
    {
        if (! $this->isTdsEnabled($companyId)) {
            return 0.0;
        }

        $fy = FinancialYear::forDate(Carbon::create($year, $month, 1));

        $declaration = IncomeTaxDeclaration::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $fy)
            ->whereIn('status', [
                IncomeTaxDeclaration::STATUS_SUBMITTED,
                IncomeTaxDeclaration::STATUS_VERIFIED,
            ])
            ->with('items')
            ->first();

        if (! $declaration) {
            return 0.0;
        }

        $useVerified = $declaration->status === IncomeTaxDeclaration::STATUS_VERIFIED;
        $projection = $this->projectTax($declaration, $useVerified);

        return (float) $projection['monthly_tds'];
    }

    public function recordTdsDeduction(
        int $companyId,
        int $userId,
        int $payrollCycleId,
        int $employeePayrollId,
        int $month,
        int $year,
        float $tdsAmount,
        float $grossForMonth
    ): EmployeeTdsRecord {
        $fy = FinancialYear::forDate(Carbon::create($year, $month, 1));
        $previousSum = (float) EmployeeTdsRecord::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $fy)
            ->where('employee_payroll_id', '!=', $employeePayrollId)
            ->sum('tds_amount');

        $cumulative = $previousSum + $tdsAmount;

        return EmployeeTdsRecord::withoutGlobalScopes()->updateOrCreate(
            ['employee_payroll_id' => $employeePayrollId],
            [
                'company_id' => $companyId,
                'user_id' => $userId,
                'financial_year' => $fy,
                'payroll_cycle_id' => $payrollCycleId,
                'month' => $month,
                'year' => $year,
                'tds_amount' => $tdsAmount,
                'cumulative_tds' => $cumulative,
                'gross_for_month' => $grossForMonth,
            ]
        );
    }

    public function cumulativeTdsForYear(int $companyId, int $userId, string $financialYear): float
    {
        return (float) EmployeeTdsRecord::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $financialYear)
            ->sum('tds_amount');
    }

    public function form16Data(int $companyId, int $userId, string $financialYear): array
    {
        $declaration = IncomeTaxDeclaration::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $financialYear)
            ->where('status', IncomeTaxDeclaration::STATUS_VERIFIED)
            ->with(['items', 'user', 'plan'])
            ->firstOrFail();

        $tdsRecords = EmployeeTdsRecord::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('financial_year', $financialYear)
            ->orderBy('year')
            ->orderBy('month')
            ->get();

        $totalTds = (float) $tdsRecords->sum('tds_amount');

        return [
            'declaration' => $declaration,
            'tds_records' => $tdsRecords,
            'total_tds_deducted' => $totalTds,
            'financial_year' => $financialYear,
            'period_from' => FinancialYear::startDate($financialYear)->format('d M Y'),
            'period_to' => FinancialYear::endDate($financialYear)->format('d M Y'),
        ];
    }

    public function isTdsEnabled(int $companyId): bool
    {
        $settings = $this->companyPayrollSettings($companyId);

        return ($settings['payroll_tds_enabled'] ?? '0') === '1';
    }

    protected function resolvePlan(int $companyId, string $regime): ?IncomeTaxPlan
    {
        $settings = $this->companyPayrollSettings($companyId);
        $key = $regime === 'old' ? 'income_tax_plan_old_id' : 'income_tax_plan_new_id';
        $planId = (int) ($settings[$key] ?? 0);

        if ($planId > 0) {
            $plan = IncomeTaxPlan::query()->where('id', $planId)->where('is_active', true)->first();
            if ($plan) {
                return $plan;
            }
        }

        $code = $regime === 'old' ? 'OLD' : 'NEW';

        return IncomeTaxPlan::query()
            ->where('is_active', true)
            ->where(function ($q) use ($code) {
                $q->where('plan_code', 'like', '%IND-' . $code . '%')
                    ->orWhere('plan_code', 'like', '%' . $code . '%');
            })
            ->orderByRaw("CASE WHEN plan_code LIKE 'IND-%' THEN 0 ELSE 1 END")
            ->orderByDesc('effective_from')
            ->orderBy('id')
            ->first()
            ?? IncomeTaxPlan::query()->where('is_active', true)->orderBy('id')->first();
    }

    /**
     * @return array<string, string>
     */
    protected function companyPayrollSettings(int $companyId): array
    {
        if (isset($this->settingsCache[$companyId])) {
            return $this->settingsCache[$companyId];
        }

        return $this->settingsCache[$companyId] = CompanySetting::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->pluck('value', 'key')
            ->all();
    }
}
