<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\SalaryComponent;
use App\Models\SalaryGroup;
use App\Models\SalaryGroupComponent;
use App\Models\User;
use App\Services\SalaryRevisionService;
use App\Support\FormTextRules;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;

class SalaryGroupController extends Controller
{
    public function __construct(
        protected SalaryRevisionService $salaryRevisionService,
    ) {}
    /**
     * @return array<int, string>
     */
    private function allowedGroupValueTypes(): array
    {
        return ['fixed', 'variable', 'basic_percent', 'ctc_percent'];
    }

    /**
     * Template IDs from catalog for "add new" rows (always create a new salary_components row on save).
     */
    private function addNewComponentTemplateIds(): array
    {
        return [-3001, -3002];
    }

    private function isFixedStructureComponent(?SalaryComponent $component): bool
    {
        if (! $component || ($component->type ?? '') !== 'earning') {
            return false;
        }
        $raw = strtolower(trim((string) $component->name));

        return in_array($raw, ['basic salary', 'house rent allowance', 'hra'], true)
            || str_contains($raw, 'house rent');
    }

    /**
     * @return array<string, mixed>
     */
    private function salaryComponentLegacyDefaults(): array
    {
        $values = [];
        foreach (['default_amount', 'monthly', 'weekly', 'bi_weekly', 'semi_monthly'] as $column) {
            if (Schema::hasColumn('salary_components', $column)) {
                $values[$column] = 0;
            }
        }
        if (Schema::hasColumn('salary_components', 'isAdded')) {
            $values['isAdded'] = true;
        }
        if (Schema::hasColumn('salary_components', 'is_taxable')) {
            $values['is_taxable'] = false;
        }

        return $values;
    }

    public function index()
    {
        $salaryGroups = SalaryGroup::withCount('users')->orderBy('name')->paginate(15);
        return view('salary-groups.index', compact('salaryGroups'));
    }

    public function create()
    {
        $existingRows = collect(old('components', []))
            ->map(function (array $row) {
                $rawType = strtolower((string) ($row['type'] ?? 'earning'));
                $normalizedType = in_array($rawType, ['deduction', 'deductions'], true) ? 'deduction' : 'earning';

                return [
                    'salary_component_id' => (int) ($row['salary_component_id'] ?? 0),
                    'name' => (string) ($row['name'] ?? ''),
                    'type' => $normalizedType,
                    'value_type' => (string) ($row['value_type'] ?? 'fixed'),
                    'value' => isset($row['value']) ? (float) $row['value'] : 0,
                    'is_fixed' => (bool) ($row['is_fixed'] ?? false),
                    'is_custom' => (bool) ($row['is_custom'] ?? false),
                    'is_locked' => (bool) ($row['is_locked'] ?? false),
                ];
            })
            ->values();

        return view('salary-groups.create', compact('existingRows'));
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => FormTextRules::notNumericOnly(true, 191),
            'components' => 'nullable|array',
            'components.*.salary_component_id' => 'required_with:components|integer',
            'components.*.name' => array_merge(
                ['required_with:components'],
                array_slice(FormTextRules::notNumericOnly(true, 191), 1)
            ),
            'components.*.type' => 'required_with:components|string|in:earning,earnings,deduction,deductions',
            'components.*.value_type' => 'required_with:components|in:' . implode(',', $this->allowedGroupValueTypes()),
            'components.*.value' => 'nullable|numeric|min:0',
            'components.*.is_custom' => 'nullable|boolean',
        ], FormTextRules::messages());

        $salaryGroup = SalaryGroup::create([
            'name' => $validated['name'],
            'company_id' => auth()->user()->company_id,
        ]);

        if (array_key_exists('components', $validated)) {
            $this->syncSalaryGroupComponents($salaryGroup, $validated['components'] ?? [], (int) auth()->user()->company_id);
        }

        if ($request->expectsJson()) {
            return response()->json(['id' => $salaryGroup->id, 'name' => $salaryGroup->name], 201);
        }

        return redirect()->route('salary-groups.index')->with('success', 'Salary group created successfully.');
    }

    public function edit(SalaryGroup $salaryGroup)
    {
        $salaryGroup->load(['users', 'salaryGroupComponents.salaryComponent']);
        $users = User::where('company_id', auth()->user()->company_id)
                        ->forActiveDirectory()
                        ->where(function ($query) use ($salaryGroup) {
                            $query->where('salary_group_id', '!=', $salaryGroup->id)
                                ->orWhereNull('salary_group_id');
                        })->orderBy('name')->get();
        $salaryComponents = SalaryComponent::where('company_id', auth()->user()->company_id)->orderBy('type')->orderBy('name')->get();
        $componentsByType = [
            'earning' => $salaryGroup->salaryGroupComponents->filter(fn ($row) => ($row->salaryComponent->type ?? '') === 'earning')->values(),
            'deduction' => $salaryGroup->salaryGroupComponents->filter(fn ($row) => ($row->salaryComponent->type ?? '') === 'deduction')->values(),
        ];
        $existingRows = $salaryGroup->salaryGroupComponents->map(function (SalaryGroupComponent $row) {
            $component = $row->salaryComponent;
            if (! $component) {
                return null;
            }
            $rawType = strtolower((string) ($component->type ?? 'earning'));
            $normalizedType = $rawType === 'deductions' ? 'deduction' : ($rawType === 'earnings' ? 'earning' : $rawType);
            $valueType = (string) ($row->value_type ?: ($row->percentage !== null ? 'basic_percent' : 'fixed'));
            $value = (float) ($row->value ?? ($row->percentage ?? $row->amount ?? 0));
            $isFixed = $this->isFixedStructureComponent($component);

            return [
                'salary_component_id' => (int) $row->salary_component_id,
                'name' => (string) ($component->name ?? ''),
                'type' => $normalizedType,
                'value_type' => $valueType,
                'value' => $value,
                'is_fixed' => $isFixed,
                'is_custom' => false,
                'is_locked' => $isFixed,
            ];
        })->filter()->values();
        return view('salary-groups.edit', compact('salaryGroup', 'users', 'salaryComponents', 'componentsByType', 'existingRows'));
    }

    public function update(Request $request, SalaryGroup $salaryGroup)
    {
        $validated = $request->validate([
            'name' => FormTextRules::notNumericOnly(true, 191),
            'user_ids' => 'nullable|array',
            'user_ids.*' => 'exists:users,id',
            'components' => 'nullable|array',
            'components.*.salary_component_id' => 'required_with:components|integer',
            'components.*.name' => array_merge(
                ['required_with:components'],
                array_slice(FormTextRules::notNumericOnly(true, 191), 1)
            ),
            'components.*.type' => 'required_with:components|string|in:earning,earnings,deduction,deductions',
            'components.*.value_type' => 'required_with:components|in:' . implode(',', $this->allowedGroupValueTypes()),
            'components.*.value' => 'nullable|numeric|min:0',
            'components.*.is_custom' => 'nullable|boolean',
            'deleted_component_ids' => 'nullable|array',
            'deleted_component_ids.*' => 'integer|min:1',
        ], FormTextRules::messages());
        $salaryGroup->update([
            'name' => $validated['name'],
        ]);
        $companyId = auth()->user()->company_id;
        if (! empty($validated['user_ids'])) {
            User::where('company_id', $companyId)
                ->forActiveDirectory()
                ->whereIn('id', $validated['user_ids'])
                ->update(['salary_group_id' => $salaryGroup->id]);
        }
        $deletedComponentIds = collect($validated['deleted_component_ids'] ?? [])
            ->map(fn ($id) => (int) $id)
            ->filter(fn ($id) => $id > 0)
            ->unique()
            ->values();
        if ($deletedComponentIds->isNotEmpty()) {
            $deletableIds = SalaryComponent::where('company_id', $companyId)
                ->whereIn('id', $deletedComponentIds->all())
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->all();
            if (! empty($deletableIds)) {
                SalaryGroupComponent::where('salary_group_id', $salaryGroup->id)
                    ->whereIn('salary_component_id', $deletableIds)
                    ->delete();
                SalaryComponent::where('company_id', $companyId)
                    ->whereIn('id', $deletableIds)
                    ->delete();
            }
        }
        if (array_key_exists('components', $validated)) {
            $this->syncSalaryGroupComponents($salaryGroup, $validated['components'] ?? [], $companyId);
        }

        if(! empty($validated['user_ids'])){
            $this->salaryRevisionService->callCreateSalaryRevision($validated['user_ids'], $salaryGroup->id, $companyId);
        }
        return redirect()->route('salary-groups.index')->with('success', 'Salary group updated successfully.');
    }

    public function storeComponent(Request $request, SalaryGroup $salaryGroup)
    {
        $validated = $request->validate([
            'salary_component_id' => 'required|exists:salary_components,id',
            'value_type' => 'required|in:' . implode(',', $this->allowedGroupValueTypes()),
            'value' => 'nullable|numeric|min:0',
        ]);
        $companyId = auth()->user()->company_id;
        if ($salaryGroup->company_id !== $companyId) {
            abort(403);
        }
        $component = SalaryComponent::where('company_id', $companyId)->findOrFail((int) $validated['salary_component_id']);
        $inputType = (string) $validated['value_type'];
        $inputValue = $request->filled('value') ? (float) $validated['value'] : null;
        $alreadyExists = $salaryGroup->salaryGroupComponents()
            ->where('salary_component_id', (int) $validated['salary_component_id'])
            ->exists();
        if ($alreadyExists) {
            return redirect()->route('salary-groups.edit', $salaryGroup)
                ->withErrors(['salary_component_id' => 'This component is already added in the salary group.'])
                ->withInput();
        }
        if (in_array($inputType, ['basic_percent', 'ctc_percent'], true) && ($inputValue === null || $inputValue > 100)) {
            return redirect()->route('salary-groups.edit', $salaryGroup)
                ->withErrors(['value' => 'Percentage value must be between 0 and 100.'])
                ->withInput();
        }
        if ($inputType === 'basic_percent') {
            $hasBasicEarning = $salaryGroup->salaryGroupComponents()
                ->whereHas('salaryComponent', fn ($q) => $q->where('type', 'earning')->where('name', 'like', '%basic%'))
                ->exists();
            if (! $hasBasicEarning && stripos((string) $component->name, 'basic') === false) {
                return redirect()->route('salary-groups.edit', $salaryGroup)
                    ->withErrors(['value' => 'Add a BASIC earning component before using Basic Percent value type.'])
                    ->withInput();
            }
        }

        $legacyAmount = 0.0;
        $legacyPercentage = null;
        if (in_array($inputType, ['fixed', 'variable', 'balancing_ctc'], true)) {
            $legacyAmount = (float) ($inputValue ?? 0);
        } elseif ($inputType === 'basic_percent') {
            $legacyPercentage = (float) ($inputValue ?? 0);
        }

        $maxOrder = $salaryGroup->salaryGroupComponents()->max('display_order') ?? 0;
        SalaryGroupComponent::create([
            'company_id' => $companyId,
            'salary_group_id' => $salaryGroup->id,
            'salary_component_id' => $validated['salary_component_id'],
            'value_type' => $inputType,
            'value' => $inputValue,
            'amount' => $legacyAmount,
            'percentage' => $legacyPercentage,
            'display_order' => $maxOrder + 1,
        ]);
        return redirect()->route('salary-groups.edit', $salaryGroup)->with('success', 'Salary component added to group.');
    }

    public function destroyComponent(SalaryGroup $salaryGroup, SalaryGroupComponent $salaryGroupComponent)
    {
        $companyId = auth()->user()->company_id;
        if ($salaryGroup->company_id !== $companyId || $salaryGroupComponent->salary_group_id !== $salaryGroup->id) {
            abort(403);
        }
        $salaryGroupComponent->delete();
        return redirect()->route('salary-groups.edit', $salaryGroup)->with('success', 'Component removed from group.');
    }

    /**
     * @return array{
     *   rows: array<int, array{name:string, monthly:float, annual:float, type:string}>,
     *   monthly_total: float,
     *   annual_total: float,
     *   effective_date: ?string
     * }
     */
    private function buildSalaryStructureForGroup(SalaryGroup $salaryGroup): array
    {
        $components = $salaryGroup->salaryGroupComponents;
        $basicComponent = $components->first(
            fn ($sgc) => $sgc->salaryComponent && ($sgc->salaryComponent->type ?? '') === 'earning' && stripos((string) ($sgc->salaryComponent->name ?? ''), 'basic') !== false
        );
        $basicBase = (float) ($basicComponent?->value ?? $basicComponent?->amount ?? 0);
        $ctcBase = 0.0;

        $rows = [];
        foreach ($components as $sgc) {
            $monthly = 0.0;
            $valueType = (string) ($sgc->value_type ?: ($sgc->percentage !== null ? 'basic_percent' : 'fixed'));
            $value = (float) ($sgc->value ?? ($sgc->percentage ?? $sgc->amount ?? 0));
            if (in_array($valueType, ['fixed', 'variable'], true)) {
                $monthly = $value;
            } elseif ($valueType === 'basic_percent') {
                $monthly = round($basicBase * ($value / 100), 2);
            } elseif ($valueType === 'ctc_percent') {
                $monthly = round($ctcBase * ($value / 100), 2);
            } elseif ($valueType === 'balancing_ctc') {
                $monthly = round(max(0, $value - $ctcBase), 2);
            }
            if (($sgc->salaryComponent->type ?? '') === 'earning') {
                $ctcBase += max(0, $monthly);
            }
            $rows[] = [
                'name' => (string) ($sgc->salaryComponent->name ?? 'Component'),
                'monthly' => round(max(0, $monthly), 2),
                'annual' => round(max(0, $monthly) * 12, 2),
                'type' => (string) ($sgc->salaryComponent->type ?? 'other'),
            ];
        }

        return [
            'rows' => $rows,
            'monthly_total' => round(array_sum(array_map(fn ($row) => (float) $row['monthly'], $rows)), 2),
            'annual_total' => round(array_sum(array_map(fn ($row) => (float) $row['annual'], $rows)), 2),
            'effective_date' => $salaryGroup->updated_at ? $salaryGroup->updated_at->format('M j, Y') : null,
        ];
    }

    public function salaryComponents(): JsonResponse
    {
        $fallback = $this->fallbackSalaryComponentsCatalog();

        return response()->json([
            'fixed' => $fallback['fixed'],
            'dropdown' => $fallback['dropdown'],
        ]);
    }

    /**
     * @return array{fixed: array<int, array<string, mixed>>, dropdown: array<int, array<string, mixed>>}
     */
    private function fallbackSalaryComponentsCatalog(): array
    {
        return [
            'fixed' => [
                ['id' => -1001, 'name' => 'Basic Salary', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '50', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -1002, 'name' => 'House Rent Allowance', 'type' => 'earnings', 'value_type' => 'basic_percent', 'monthly' => '40', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
            ],
            'dropdown' => [
                ['id' => -2001, 'name' => 'Conveyance Allowance', 'type' => 'earnings', 'value_type' => 'basic_percent', 'monthly' => '5', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2002, 'name' => 'Gratuity', 'type' => 'earnings', 'value_type' => 'basic_percent', 'monthly' => '0.0481', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2003, 'name' => 'Medical Allowance', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '5', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2004, 'name' => 'Leave Travel Allowance', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '0.5', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2005, 'name' => 'New Monthly Incentive', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '0.05', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2006, 'name' => 'Phone Reimbursement', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '0', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2007, 'name' => 'Vehicle Allowance', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '0', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -2008, 'name' => 'Monthly Incentive', 'type' => 'earnings', 'value_type' => 'ctc_percent', 'monthly' => '0', 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -3001, 'name' => 'Add New Earning Component', 'type' => 'earnings', 'value_type' => 'fixed', 'monthly' => '0', 'addable' => true, 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
                ['id' => -3002, 'name' => 'Add New Deduction Component', 'type' => 'deductions', 'value_type' => 'fixed', 'monthly' => '0', 'addable' => true, 'isAdded' => true, 'weekly' => 0, 'bi_weekly' => 0, 'semi_monthly' => 0],
            ],
        ];
    }

    /**
     * @param array<int, array{salary_component_id:int|string, value_type:string, value:mixed}> $rows
     */
    private function syncSalaryGroupComponents(SalaryGroup $salaryGroup, array $rows, int $companyId): void
    {
        $salaryGroup->salaryGroupComponents()->delete();
        $order = 1;
        $seenComponentIds = [];

        foreach ($rows as $row) {
            $componentId = (int) ($row['salary_component_id'] ?? 0);
            $isCustom = ! empty($row['is_custom']) || in_array($componentId, $this->addNewComponentTemplateIds(), true);
            $name = trim((string) ($row['name'] ?? ''));
            if ($isCustom && $name === '') {
                continue;
            }

            $component = $componentId > 0
                ? SalaryComponent::where('company_id', $companyId)->find($componentId)
                : null;

            if (! $component) {
                $rawType = strtolower((string) ($row['type'] ?? 'earning'));
                $normalizedType = in_array($rawType, ['deduction', 'deductions'], true) ? 'deduction' : 'earning';
                if ($isCustom) {
                    $component = SalaryComponent::create(
                        array_filter(array_merge([
                            'company_id' => $companyId,
                            'name' => $name,
                            'type' => $normalizedType,
                            'value_type' => (string) ($row['value_type'] ?? 'fixed'),
                        ], $this->salaryComponentLegacyDefaults()), static fn ($v) => $v !== null)
                    );
                } else {
                    $component = SalaryComponent::firstOrCreate(
                        [
                            'company_id' => $companyId,
                            'name' => $name !== '' ? $name : 'Component',
                        ],
                        array_filter(array_merge([
                            'type' => $normalizedType,
                            'value_type' => (string) ($row['value_type'] ?? 'fixed'),
                        ], $this->salaryComponentLegacyDefaults()), static fn ($v) => $v !== null)
                    );
                }
            }
            if (in_array((int) $component->id, $seenComponentIds, true)) {
                continue;
            }
            $seenComponentIds[] = (int) $component->id;

            $inputType = (string) ($row['value_type'] ?? 'fixed');
            $inputValue = isset($row['value']) ? (float) $row['value'] : null;
            if (in_array($inputType, ['basic_percent', 'ctc_percent'], true)) {
                $inputValue = max(0, min(100, (float) $inputValue));
            }

            $legacyAmount = 0.0;
            $legacyPercentage = null;
            if (in_array($inputType, ['fixed', 'variable', 'balancing_ctc'], true)) {
                $legacyAmount = (float) ($inputValue ?? 0);
            } elseif ($inputType === 'basic_percent') {
                $legacyPercentage = (float) ($inputValue ?? 0);
            }

            SalaryGroupComponent::create([
                'company_id' => $companyId,
                'salary_group_id' => $salaryGroup->id,
                'salary_component_id' => (int) $component->id,
                'value_type' => $inputType,
                'value' => $inputValue,
                'amount' => $legacyAmount,
                'percentage' => $legacyPercentage,
                'display_order' => $order++,
            ]);
        }
    }
}
