package de.avatic.lcc.service.access;

import de.avatic.lcc.dto.generic.PropertyDTO;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.model.db.properties.SystemPropertyMappingId;
import de.avatic.lcc.model.db.rates.ValidityPeriodState;
import de.avatic.lcc.repositories.calculation.CalculationJobRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.properties.PropertySetRepository;
import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer;
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
import de.avatic.lcc.util.exception.badrequest.NotFoundException.NotFoundType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
 * Service for managing properties and property sets within the application.
 * <p>
 * This service provides functionality to:
 * <ul>
 *   <li>Set property values for specific mappings</li>
 *   <li>List properties filtered by property set ID</li>
 *   <li>List all property sets with their validity periods</li>
 *   <li>Invalidate property sets</li>
 * </ul>
 * <p>
 * The service interacts with {@link PropertyRepository} and {@link PropertySetRepository}
 * to perform CRUD operations on properties and property sets.
 */
@Service
public class PropertyService {

    private final PropertyRepository propertyRepository;
    private final PropertySetRepository propertySetRepository;
    private final ValidityPeriodTransformer validityPeriodTransformer;
    private final PropertyValidationService propertyValidationService;
    private final CalculationJobRepository calculationJobRepository;


    /**
     * Constructs the PropertyService with the provided repositories.
     *
     * @param propertyRepository        the repository to manage properties
     * @param propertySetRepository     the repository to manage property sets
     * @param validityPeriodTransformer the transformer to convert property sets to validity period DTOs
     */
    public PropertyService(PropertyRepository propertyRepository, PropertySetRepository propertySetRepository, ValidityPeriodTransformer validityPeriodTransformer, PropertyValidationService propertyValidationService, CalculationJobRepository calculationJobRepository) {
        this.propertyRepository = propertyRepository;
        this.propertySetRepository = propertySetRepository;
        this.validityPeriodTransformer = validityPeriodTransformer;
        this.propertyValidationService = propertyValidationService;
        this.calculationJobRepository = calculationJobRepository;
    }

    /**
     * Sets a property value for a given mapping ID.
     * <p>
     * This method updates the property value in the current draft set for the specified mapping ID.
     * Note: Property value validation is pending implementation.
     *
     * @param propertyId the identifier of the property to be updated
     * @param value      the new value to be set for the property
     */
    @Transactional
    public void setProperties(String propertyId, String value) {

        try {
            var mappingId = SystemPropertyMappingId.valueOf(propertyId.toUpperCase());
            propertyValidationService.validateProperty(mappingId, value);
            propertyRepository.setProperty(propertySetRepository.getDraftSetId(), mappingId.name(), value);
        } catch (IllegalArgumentException e) {
            throw new NotFoundException(NotFoundType.PROPERTY, "id", propertyId, e);
        }
    }

    /**
     * Retrieves a list of properties based on the specified property set ID.
     * <p>
     * If the property set ID is 0, all properties from both the current valid set
     * and current draft set are returned. Otherwise, only properties from the
     * specified property set are returned.
     *
     * @param propertySetId the ID of the property set (0 for all properties)
     * @return a list of properties as {@link PropertyDTO} objects
     */
    public List<PropertyDTO> listProperties(Integer propertySetId) {


        var state = propertySetId != 0 ? propertySetRepository.getState(propertySetId) : null;

        if (propertySetId == 0 || (state != ValidityPeriodState.EXPIRED && state != ValidityPeriodState.INVALID)) {
            return propertyRepository.listProperties();
        }

        return propertyRepository.listPropertiesBySetId(propertySetId);
    }

    /**
     * Retrieves a list of all property sets, mapping them to validity period data transfer objects.
     * <p>
     * This method fetches all property sets from the repository and transforms each of them
     * into a {@link ValidityPeriodDTO} using the injected transformer.
     *
     * @return a list of property sets as {@link ValidityPeriodDTO} objects
     */
    public List<ValidityPeriodDTO> listPropertySets() {
        return propertySetRepository.listPropertySets().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList();
    }

    /**
     * Invalidates a property set by its ID.
     *
     * @param id the ID of the property set to invalidate
     */
    public void invalidate(Integer id) {
        if (!propertySetRepository.invalidateById(id))
            throw new NotFoundException(NotFoundType.EXPIRED_PROPERTY_SET, "id", id.toString());

        calculationJobRepository.invalidateByPropertySetId(id);

    }

    @SuppressWarnings("unchecked")
    public <T> Optional<T> getProperty(SystemPropertyMappingId mappingId) {
        var prop = propertyRepository.getPropertyByMappingId(mappingId);

        if (prop.isEmpty())
            return Optional.empty();

        var property = prop.get();
        var propValue = property.getCurrentValue();
        var dataType = property.getDataType();

        if (propValue == null)
            return Optional.empty();

        try {
            Object convertedValue = convertStringByDataType(propValue, dataType);
            return Optional.of((T) convertedValue);
        } catch (ClassCastException e) {
            // The caller expected a different type than what the dataType indicates
            return Optional.empty();
        } catch (Exception e) {
            // Parsing error
            return Optional.empty();
        }
    }

    private Object convertStringByDataType(String value, String dataType) {
        return switch (dataType.toUpperCase()) {
            case "TEXT", "ENUMERATION" -> value;
            case "CURRENCY", "PERCENTAGE" -> Double.valueOf(value);
            case "BOOLEAN" -> Boolean.valueOf(value);
            case "INT" -> Integer.valueOf(value);
            default -> throw new IllegalArgumentException("Unsupported data type: " + dataType);
        };
    }
}
