package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;

@Service
public class LegacyXmlSystemAuthService {

    private static final Logger log = LoggerFactory.getLogger(LegacyXmlSystemAuthService.class);
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.BASIC_ISO_DATE;
    private static final List<String> SESSION_QUERIES = List.of(
            "SELECT COUNT(*) FROM meta_object.metaobject " +
                    "WHERE mo_obj_type = 'system' AND mo_id_ref_db = ? AND mo_obj_id = ? AND mo_hash = ?",
            "SELECT COUNT(*) FROM metaobject " +
                    "WHERE mo_obj_type = 'system' AND mo_id_ref_db = ? AND mo_obj_id = ? AND mo_hash = ?"
    );

    private final JdbcTemplate jdbcTemplate;
    private final ParameterService parameterService;

    public LegacyXmlSystemAuthService(JdbcTemplate jdbcTemplate,
                                      ParameterService parameterService) {
        this.jdbcTemplate = jdbcTemplate;
        this.parameterService = parameterService;
    }

    public void authenticate(String sessionId, String passphrase) {
        if (isBlank(sessionId) || isBlank(passphrase)) {
            throw new LegacyXmlRequestException("101", "Die Authentifizierungsdaten sind nicht in Ordnung.");
        }
        String dbInstanceNo = parameter(ParameterKeys.GLOBAL_UNIQUE_DB_INSTANCE_NO);
        String systemId = parameter(ParameterKeys.GLOBAL_SESSION_SYSTEM_ID);
        String salt = parameter(ParameterKeys.GLOBAL_SESSION_SALT);
        if (isBlank(dbInstanceNo)
                || isBlank(systemId)
                || isBlank(salt)
                || !hasValidSessionHash(dbInstanceNo, systemId, sessionId.trim())
                || !hasValidPassphrase(passphrase.trim(), salt, LocalDate.now())) {
            throw new LegacyXmlRequestException("101", "Die Authentifizierungsdaten sind nicht in Ordnung.");
        }
    }

    private String parameter(String key) {
        String value = parameterService.getParameterValue(key, 0L);
        if (isBlank(value)) {
            value = parameterService.getParameterValueWithFallback(key, 0L);
        }
        return value;
    }

    private boolean hasValidSessionHash(String dbInstanceNo, String systemId, String sessionId) {
        for (String sql : SESSION_QUERIES) {
            try {
                Integer count = jdbcTemplate.queryForObject(Objects.requireNonNull(sql), Integer.class, Objects.requireNonNull(dbInstanceNo), systemId, sessionId);
                if (count != null && count > 0) {
                    return true;
                }
            } catch (Exception ex) {
                log.debug("Legacy XML system session query failed for SQL [{}]: {}", sql, ex.getMessage());
            }
        }
        return false;
    }

    boolean hasValidPassphrase(String passphrase, String salt, LocalDate currentDate) {
        if (isBlank(passphrase) || isBlank(salt) || currentDate == null || passphrase.length() < 22) {
            return false;
        }
        String currDate = currentDate.format(DATE_FORMAT);
        String tmpDate = "20"
                + safeSlice(passphrase, 12, 14)
                + safeSlice(passphrase, 5, 7)
                + safeSlice(passphrase, 0, 2);
        String tmpDate2 = "20" + safeSlice(passphrase, 12, 18);
        String tmpSalt = safeSlice(passphrase, 7, 12)
                + safeSlice(passphrase, 2, 5)
                + safeSlice(passphrase, 18, 22);
        return currDate.equals(tmpDate) && currDate.equals(tmpDate2) && salt.equals(tmpSalt);
    }

    private String safeSlice(String value, int from, int to) {
        if (value == null || from < 0 || to < from || value.length() < to) {
            return "";
        }
        return value.substring(from, to);
    }

    private boolean isBlank(String value) {
        return value == null || value.isBlank();
    }
}
