package de.votian.services.service;

import de.votian.services.entity.Parameter;
import de.votian.services.repository.ParameterRepository;
import de.votian.services.repository.EmployeeRepository;
import de.votian.services.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * Service for accessing application parameters.
 * Implements the same fallback logic as the PHP getParameterValue() function:
 *
 * 1. Try exact match: emp_id + par_key + hq_id
 * 2. If emp_id > 0 and no match: try emp_id + par_key + user's original hq_id
 *
 * Also provides:
 * - getObjectBasedParameterValue() with cascading fallback (PHP equivalent)
 * - loadGlobalParameters() for bulk-loading (PHP defineGlobalParameters() equivalent)
 */
@Service
@Transactional(readOnly = true)
public class ParameterService {

    private final ParameterRepository parameterRepository;
    private final EmployeeRepository employeeRepository;
    private final UserRepository userRepository;

    public ParameterService(ParameterRepository parameterRepository,
                            EmployeeRepository employeeRepository,
                            UserRepository userRepository) {
        this.parameterRepository = parameterRepository;
        this.employeeRepository = employeeRepository;
        this.userRepository = userRepository;
    }

    // ========================================================================
    // Core parameter access (PHP getParameterValue equivalent)
    // ========================================================================

    /**
     * Get a parameter value with PHP-compatible fallback logic.
     *
     * Fallback order:
     * 1. emp_id + key + hq_id
     * 2. If emp_id > 0: emp_id + key + user's original hq_id
     *
     * @param empId  Employee ID (0 for global/headquarters parameters)
     * @param key    Parameter key
     * @param hqId   Headquarters ID
     * @return Parameter value or empty string if not found
     */
    public String getParameterValue(long empId, String key, long hqId) {
        // 1. Try exact match
        Optional<Parameter> param = parameterRepository.findByKeyAndHqAndEmployee(key, hqId, empId);
        if (param.isPresent() && param.get().getValue() != null && !param.get().getValue().isEmpty()) {
            return param.get().getValue();
        }

        // 2. Fallback: if emp_id > 0, try user's original headquarters
        if (empId > 0) {
            Long usrHqId = getEmployeeOriginalHqId(empId);
            if (usrHqId != null && usrHqId != hqId) {
                param = parameterRepository.findByKeyAndHqAndEmployee(key, usrHqId, empId);
                if (param.isPresent() && param.get().getValue() != null && !param.get().getValue().isEmpty()) {
                    return param.get().getValue();
                }
            }
        }

        return "";
    }

    /**
     * Simplified overload: get global parameter for a headquarters (emp_id = 0).
     */
    public String getParameterValue(String key, long hqId) {
        return getParameterValue(0L, key, hqId);
    }

    /**
     * Get parameter with automatic fallback to global (hq_id = 0) if not found for specific HQ.
     * This is the most common pattern in PHP code:
     *   $val = getParameterValue("0", "KEY", $hq_id);
     *   if ($val == "") : $val = getParameterValue("0", "KEY", "0"); endif;
     */
    public String getParameterValueWithFallback(String key, long hqId) {
        String value = getParameterValue(0L, key, hqId);
        if (value.isEmpty() && hqId != 0) {
            value = getParameterValue(0L, key, 0L);
        }
        return value;
    }

    /**
     * Get parameter with full fallback chain: emp+hq → emp+0 → 0+hq → 0+0
     */
    public String getParameterValueFullFallback(long empId, String key, long hqId) {
        String value = getParameterValue(empId, key, hqId);
        if (value.isEmpty() && empId > 0) {
            value = getParameterValue(0L, key, hqId);
        }
        if (value.isEmpty() && hqId > 0) {
            value = getParameterValue(empId, key, 0L);
        }
        if (value.isEmpty() && (empId > 0 || hqId > 0)) {
            value = getParameterValue(0L, key, 0L);
        }
        return value;
    }

    // ========================================================================
    // Object-based parameter access (PHP getObjectBasedParameterValue equivalent)
    // ========================================================================

    /**
     * Get a parameter value for a specific object (e.g., customer, courier) with cascading fallback.
     *
     * PHP equivalent: getObjectBasedParameterValue($key, $objId, $hqId, $empId, $noGlobalCheck)
     *
     * Fallback order:
     * 1. key_objId + empId + hqId
     * 2. key_objId + 0     + hqId
     * 3. key_objId + empId + 0
     * 4. key_objId + 0     + 0
     * 5. key       + empId + hqId  (if noGlobalCheck is false)
     * 6. key       + 0     + hqId
     * 7. key       + empId + 0
     * 8. key       + 0     + 0
     */
    public String getObjectBasedParameterValue(String key, Long objId, long hqId, long empId, boolean noGlobalCheck) {
        String value = "";

        if (objId != null && objId > 0) {
            String objKey = key + "_" + objId;
            value = getParameterValue(empId, objKey, hqId);
            if (value.isEmpty()) value = getParameterValue(0L, objKey, hqId);
            if (value.isEmpty()) value = getParameterValue(empId, objKey, 0L);
            if (value.isEmpty()) value = getParameterValue(0L, objKey, 0L);
        }

        if (value.isEmpty() && !noGlobalCheck) {
            value = getParameterValue(empId, key, hqId);
            if (value.isEmpty()) value = getParameterValue(0L, key, hqId);
            if (value.isEmpty()) value = getParameterValue(empId, key, 0L);
            if (value.isEmpty()) value = getParameterValue(0L, key, 0L);
        }

        return value;
    }

    public String getObjectBasedParameterValue(String key, Long objId, long hqId, long empId) {
        return getObjectBasedParameterValue(key, objId, hqId, empId, false);
    }

    // ========================================================================
    // Bulk-load (PHP defineGlobalParameters equivalent)
    // ========================================================================

    /**
     * Load all global parameters for a headquarters as a key-value map.
     * Equivalent to PHP defineGlobalParameters() which defines all parameters as constants.
     *
     * @param hqId Headquarters ID
     * @return Map of parameter key to value
     */
    public Map<String, String> loadGlobalParameters(long hqId) {
        List<Parameter> params = parameterRepository.findGlobalByHq(hqId);
        Map<String, String> result = new LinkedHashMap<>();
        for (Parameter p : params) {
            // First definition wins (like PHP define() which doesn't override)
            if (!result.containsKey(p.getKey())) {
                result.put(p.getKey(), p.getValue() != null ? p.getValue() : "");
            }
        }
        return result;
    }

    /**
     * Load all parameters for a headquarters and employee, merged with globals.
     * Employee-specific values override global values.
     *
     * @param hqId  Headquarters ID
     * @param empId Employee ID
     * @return Map of parameter key to value
     */
    public Map<String, String> loadAllParameters(long hqId, long empId) {
        // Start with global parameters
        Map<String, String> result = new LinkedHashMap<>(loadGlobalParameters(hqId));

        // Also load global parameters with hq_id = 0 as fallback
        if (hqId != 0) {
            List<Parameter> globalParams = parameterRepository.findGlobalByHq(0L);
            for (Parameter p : globalParams) {
                if (!result.containsKey(p.getKey())) {
                    result.put(p.getKey(), p.getValue() != null ? p.getValue() : "");
                }
            }
        }

        // Override with employee-specific parameters
        if (empId > 0) {
            List<Parameter> empParams = parameterRepository.findAllByHqAndEmployee(hqId, empId);
            for (Parameter p : empParams) {
                result.put(p.getKey(), p.getValue() != null ? p.getValue() : "");
            }
        }

        return result;
    }

    // ========================================================================
    // Parameter write operations
    // ========================================================================

    /**
     * Set a parameter value. Creates or updates the parameter.
     * Equivalent to PHP setParameterValue().
     */
    @Transactional
    public Parameter setParameterValue(String key, String value, long hqId, long empId) {
        Optional<Parameter> existing = parameterRepository.findByKeyAndHqAndEmployee(key, hqId, empId);
        if (existing.isPresent()) {
            Parameter param = existing.get();
            param.setValue(value);
            return parameterRepository.save(param);
        } else {
            Parameter param = new Parameter();
            param.setKey(key);
            param.setValue(value);
            param.setHeadquartersId(hqId);
            param.setEmployeeId(empId);
            param.setMandatorId(0L);
            return parameterRepository.save(param);
        }
    }

    @Transactional
    public Parameter save(Parameter parameter) {
        return parameterRepository.save(Objects.requireNonNull(parameter));
    }

    @Transactional
    public void updateValue(Long parameterId, String value) {
        parameterRepository.findById(Objects.requireNonNull(parameterId)).ifPresent(p -> {
            p.setValue(value);
            parameterRepository.save(p);
        });
    }

    @Transactional
    public void deleteByKeyPattern(String keyPattern) {
        List<Parameter> params = parameterRepository.findByKey(keyPattern);
        parameterRepository.deleteAll(Objects.requireNonNull(params));
    }

    // ========================================================================
    // Query operations
    // ========================================================================

    public List<Parameter> getParametersByKey(String key) {
        return parameterRepository.findByKeyOrderByHq(key);
    }

    public List<Parameter> getParametersByKeyPrefix(String keyPrefix, long hqId) {
        return parameterRepository.findByKeyPrefixAndHq(keyPrefix, hqId);
    }

    public List<Parameter> getParametersByKeyPrefix(String keyPrefix) {
        return parameterRepository.findByKeyPrefix(keyPrefix);
    }

    // ========================================================================
    // Helper methods
    // ========================================================================

    /**
     * Get the original headquarters ID for an employee (via user table).
     * PHP equivalent: get hq_id from user for employee's usr_id.
     */
    private Long getEmployeeOriginalHqId(long empId) {
        return employeeRepository.findById(empId)
                .flatMap(emp -> userRepository.findById(Objects.requireNonNull(emp.getUserId())))
                .map(usr -> usr.getHeadquartersId())
                .orElse(null);
    }
}
