<?php

namespace Modules\HR\Services;

use Illuminate\Support\Facades\DB;
use Modules\Core\Enums\StatusEnum;
use Modules\Employee\Models\Employee;
use Modules\HR\DTO\HrSettingFilter;
use Modules\HR\Models\SalaryStructure;
use Modules\HR\Repositories\SalaryStructureItemRepository;
use Modules\HR\Repositories\SalaryStructureRepository;
use Illuminate\Support\Arr;
use Modules\HR\Enums\SalaryStructureCalculationTypeEnum;
use Modules\HR\Models\SalaryComponent;
use Illuminate\Validation\ValidationException;
use Modules\Core\Exceptions\ApiValidationException;
use Modules\Employee\DTO\EmployeeListDTO;
use Modules\Employee\DTO\EmployeeSettingFilter;

class SalaryStructureService
{

    public function __construct(
        protected SalaryStructureRepository $salaryStructureRepo,
        protected SalaryStructureItemRepository $salaryStructureItemRepo
    ) {}

    public function list(Employee $employee, HrSettingFilter $filter)
    {
        $columns = [
            'id',
            'employee_id',
            'name',
            'effective_from',
            'effective_to',
            'is_locked',
        ];

        return $this->salaryStructureRepo->all(
            $employee,
            $filter,
            $columns
        );
    }

    public function lookup(HrSettingFilter $filter)
    {
        return 'TODO: Implement lookup method in SalaryStructureService with filter';
        $columns = ['id', 'name', 'status'];
        // return $this->salaryStructureRepo->all($filter, $columns);
    }

    public function show($id): ?SalaryStructure
    {
        return $this->salaryStructureRepo->find($id);
    }

    public function store(Employee $employee, array $data)
    {
        // return DB::transaction(function () use ($employee, $data) {
        $data['employee_id'] = $employee->id;
        $items = $data['items'] ?? [];
        $this->validateItems($items);
        $salaryStructurePayload = [
            ...Arr::except($data, ['items']),
        ];

        $structure = $this->salaryStructureRepo->create($salaryStructurePayload);

        $components = SalaryComponent::whereIn(
            'id',
            collect($items)->pluck('salary_component_id')
        )->get()->keyBy('id');

        $itemPayloads = $this->structureItemPayloads(
            $components,
            $structure,
            $items
        );

        $this->salaryStructureItemRepo->createItems($itemPayloads);


        $basicSalary = $this->getBasicSalary($items, $components);
        $employee->update([
            'current_basic_salary' => $basicSalary
        ]);


        return $structure;
        // });
    }

    private function getBasicSalary(array $items, $components): float
    {
        foreach ($items as $item) {
            $component = $components[$item['salary_component_id']] ?? null;

            if ($component?->is_basic) {
                return (float) ($item['calculation_value'] ?? 0);
            }
        }

        return 0;
    }

    private function structureItemPayloads($components, SalaryStructure $structure, array $items): array
    {
        return collect($items)
            ->map(function ($item) use ($structure, $components) {

                $component = $components[$item['salary_component_id']];

                return [
                    'salary_structure_id' => $structure->id,
                    'salary_component_id' => $item['salary_component_id'],

                    'is_basic' => $component->is_basic, // auto set

                    'use_default_rule' => $item['use_default_rule'] ?? false,
                    'calculation_type' => $item['calculation_type'] ?? null,
                    'calculation_value' => $item['calculation_value'] ?? 0,
                    'formula' => $item['formula'] ?? null,
                    'calculation_basis' => $item['calculation_basis'] ?? null,
                    'status' => $item['status'] ?? StatusEnum::ACTIVE,
                    'created_at' => now(),
                    'updated_at' => now(),
                ];
            })
            ->toArray();
    }

    public function validateItems(array $items): void
    {
        $components = SalaryComponent::whereIn(
            'id',
            collect($items)->pluck('salary_component_id')->unique()
        )->get()->keyBy('id');

        $this->validateOnlyOneBasic($items, $components);

        foreach ($items as $index => $item) {

            $component = $components[$item['salary_component_id']] ?? null;

            if (!$component) {
                ApiValidationException::errors([
                    "items.$index.salary_component_id" => "Invalid salary component."
                ]);
            }

            $this->validateBasicRule($component, $item, $index);

            $this->validateCalculationRules($component, $item, $index);
        }
    }

    private function validateOnlyOneBasic(array $items, $components): void
    {
        $basicCount = collect($items)
            ->filter(function ($item) use ($components) {
                $component = $components[$item['salary_component_id']] ?? null;
                return $component?->is_basic;
            })
            ->count();

        if ($basicCount > 1) {
            ApiValidationException::errors([
                "items" => "Only one basic salary component is allowed in salary structure."
            ]);
        }
    }

    private function validateBasicRule($component, $item, $index): void
    {
        if (!$component->is_basic) {
            return;
        }

        if ($item['calculation_type'] !== SalaryStructureCalculationTypeEnum::FIXED) {
            ApiValidationException::errors([
                "items.$index.calculation_type" =>
                "Basic salary component must use FIXED calculation type."
            ]);
        }

        if (($item['use_default_rule'] ?? true) !== false) {
            ApiValidationException::errors([
                "items.$index.use_default_rule" =>
                "Basic salary component must have use_default_rule set to false."
            ]);
        }
    }

    private function validateCalculationRules($component, $item, $index): void
    {
        if ($item['calculation_type'] === SalaryStructureCalculationTypeEnum::FIXED) {
            if (is_null($item['calculation_value'])) {
                ApiValidationException::errors([
                    "items.$index.calculation_value" =>
                    "Calculation value is required for FIXED type."
                ]);
            }
        }

        if ($item['calculation_type'] === SalaryStructureCalculationTypeEnum::PERCENTAGE) {
            if (is_null($item['calculation_value'])) {
                ApiValidationException::errors([
                    "items.$index.calculation_value" => "Calculation value is required for PERCENTAGE type."
                ]);
            }
        }

        if ($item['calculation_type'] === SalaryStructureCalculationTypeEnum::FORMULA) {
            if (empty($item['formula'])) {
                ApiValidationException::errors([
                    "items.$index.formula" => "Formula is required for FORMULA type."
                ]);
            }
        }
    }

    public function update(SalaryStructure $salaryStructure, array $data)
    {
        return DB::transaction(function () use ($salaryStructure, $data) {
            return $this->salaryStructureRepo->update($salaryStructure, $data);
        });
    }

    public function updateStatus(SalaryStructure $salaryStructure, array $status)
    {
        return DB::transaction(function () use ($salaryStructure, $status) {
            return $this->salaryStructureRepo->updateStatus($salaryStructure, $status);
        });
    }

    public function getActiveSalaryStructure(Employee $employee): ?SalaryStructure
    {
        return $this->salaryStructureRepo->getActiveSalaryStructure($employee);
    }

    public function employeesList(EmployeeListDTO $filter)
    {
        $columns = ['id', 'name', 'emp_number', 'designation_id'];
        $relations = [
            'designation:id,name',
            'currentSalaryStructure:id,employee_id,name,effective_from,status',
            'currentSalaryStructure.basicItem:id,salary_structure_id,calculation_type,calculation_value,is_basic'
        ];

        return $this->salaryStructureRepo->employeesList($filter, $columns, $relations);
    }

    public function designationList(EmployeeSettingFilter $filter)
    {
        $columns = ['id', 'name', 'salary_grade_id'];
        $relations = ['salaryGrade:id,name,min_salary,max_salary'];

        return $this->salaryStructureRepo->designationList($filter, $columns, $relations);
    }
}
