package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.dto.FilterAdminDto;
import de.votian.services.dto.GroupAdminDto;
import de.votian.services.entity.AppGroup;
import de.votian.services.entity.CourierFilter;
import de.votian.services.repository.AppGroupRepository;
import de.votian.services.repository.CourierFilterRepository;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Transactional
public class GroupFilterAdminService {

    private final AppGroupRepository appGroupRepository;
    private final CourierFilterRepository courierFilterRepository;
    private final JdbcTemplate jdbcTemplate;
    private final ParameterService parameterService;

    public GroupFilterAdminService(AppGroupRepository appGroupRepository,
                                   CourierFilterRepository courierFilterRepository,
                                   JdbcTemplate jdbcTemplate,
                                   ParameterService parameterService) {
        this.appGroupRepository = appGroupRepository;
        this.courierFilterRepository = courierFilterRepository;
        this.jdbcTemplate = jdbcTemplate;
        this.parameterService = parameterService;
    }

    @Transactional(readOnly = true)
    public List<GroupAdminDto> findGroups(Long headquartersId) {
        Long mandatorId = resolveMandatorId(headquartersId);
        return appGroupRepository.findVisibleForHeadquarters(mandatorId, headquartersId).stream()
                .map(this::toGroupDto)
                .toList();
    }

    public GroupAdminDto createGroup(Long headquartersId, String name, boolean globalVisible) {
        Long mandatorId = resolveMandatorId(headquartersId);
        String normalizedName = requireName(name, "Gruppenname");
        ensureGroupNameUnique(mandatorId, normalizedName, null);

        AppGroup group = new AppGroup();
        group.setMandatorId(mandatorId);
        group.setHeadquartersId(globalVisible ? 0L : headquartersId);
        group.setName(normalizedName);
        group.setReadonly("0");
        group.setGroup("");
        return toGroupDto(appGroupRepository.save(group));
    }

    public GroupAdminDto updateGroup(Long id, Long headquartersId, String name, boolean globalVisible) {
        Long mandatorId = resolveMandatorId(headquartersId);
        AppGroup group = loadVisibleGroup(id, headquartersId, mandatorId);
        if (isFlagEnabled(group.getReadonly())) {
            throw new IllegalArgumentException("Die Gruppe ist schreibgeschuetzt.");
        }

        String normalizedName = requireName(name, "Gruppenname");
        ensureGroupNameUnique(mandatorId, normalizedName, id);
        group.setName(normalizedName);
        group.setHeadquartersId(globalVisible ? 0L : headquartersId);
        return toGroupDto(appGroupRepository.save(group));
    }

    public void deleteGroup(Long id, Long headquartersId) {
        Long mandatorId = resolveMandatorId(headquartersId);
        AppGroup group = loadVisibleGroup(id, headquartersId, mandatorId);
        if (isFlagEnabled(group.getReadonly())) {
            throw new IllegalArgumentException("Die Gruppe ist schreibgeschuetzt.");
        }

        removeTokenFromField("groups", "grp_group", numericToken(id));
        removeTokenFromField("customer", "cs_group", numericToken(id));
        removeTokenFromField("courier", "cr_group", numericToken(id));
        removeTokenFromField("employee", "emp_group", numericToken(id));
        removeTokenFromField("couriervehicle", "crvh_group", numericToken(id));
        removeTokenFromField("article", "at_group", numericToken(id));
        removeTokenFromField("service", "srv_group", numericToken(id));
        removeTokenFromField("servicetype", "srvt_group", numericToken(id));
        removeTokenFromField("branch", "br_group", numericToken(id));
        removeTokenFromField("metafieldcategory", "mtfc_group", numericToken(id));
        removeTokenFromField("metafieldtemplate", "mtft_group", numericToken(id));
        appGroupRepository.delete(group);
    }

    @Transactional(readOnly = true)
    public List<FilterAdminDto> findFilters() {
        return courierFilterRepository.findAllByOrderBySortAscShortCodeAsc().stream()
                .map(this::toFilterDto)
                .toList();
    }

    public FilterAdminDto createFilter(String shortCode,
                                       String text,
                                       Integer type,
                                       Integer status,
                                       Integer sort,
                                       String longText) {
        String normalizedShortCode = requireShortCode(shortCode);
        String normalizedText = requireName(text, "Filterbezeichnung");
        int normalizedType = normalizeFilterType(type);
        int normalizedStatus = normalizeFilterStatus(status);
        int normalizedSort = sort != null ? sort : 0;

        ensureFilterShortCodeUnique(normalizedShortCode, null);
        ensureFilterTextUnique(normalizedText, null);
        ensureFilterNotQueuedForDeletion(normalizedShortCode);

        CourierFilter filter = new CourierFilter();
        filter.setShortCode(normalizedShortCode);
        filter.setText(normalizedText);
        filter.setType(normalizedType);
        filter.setStatus(normalizedStatus);
        filter.setSort(normalizedSort);
        filter.setLongText(longText != null ? longText.trim() : "");
        filter.setVehicleTypeId(0L);
        return toFilterDto(courierFilterRepository.save(filter));
    }

    public FilterAdminDto updateFilter(Long id,
                                       String text,
                                       Integer type,
                                       Integer status,
                                       Integer sort,
                                       String longText) {
        CourierFilter filter = courierFilterRepository.findById(Objects.requireNonNull(id))
                .orElseThrow(() -> new IllegalArgumentException("Filter nicht gefunden."));

        String normalizedText = requireName(text, "Filterbezeichnung");
        ensureFilterTextUnique(normalizedText, id);
        filter.setText(normalizedText);
        filter.setType(normalizeFilterType(type));
        filter.setStatus(normalizeFilterStatus(status));
        filter.setSort(sort != null ? sort : 0);
        filter.setLongText(longText != null ? longText.trim() : "");
        return toFilterDto(courierFilterRepository.save(filter));
    }

    public void deleteFilter(Long id) {
        CourierFilter filter = courierFilterRepository.findById(Objects.requireNonNull(id))
                .orElseThrow(() -> new IllegalArgumentException("Filter nicht gefunden."));

        String token = filterToken(filter.getShortCode());
        removeTokenFromField("customer", "cs_filter", token);
        removeTokenFromField("courier", "cr_filter", token);
        removeTokenFromField("couriervehicle", "crvh_filter", token);
        queueFilterForDeletion(filter.getShortCode());
        courierFilterRepository.delete(filter);
    }

    private GroupAdminDto toGroupDto(AppGroup group) {
        GroupAdminDto dto = new GroupAdminDto();
        dto.setId(group.getId());
        dto.setName(group.getName());
        dto.setHeadquartersId(group.getHeadquartersId());
        dto.setGlobalVisible(group.getHeadquartersId() != null && group.getHeadquartersId() == 0L);
        dto.setReadonly(isFlagEnabled(group.getReadonly()));

        Map<String, Long> usages = new LinkedHashMap<>();
        usages.put("Kunden", countReferences("customer", "cs_group", numericToken(group.getId())));
        usages.put("Kuriere", countReferences("courier", "cr_group", numericToken(group.getId())));
        usages.put("Mitarbeiter", countReferences("employee", "emp_group", numericToken(group.getId())));
        usages.put("Fahrzeuge", countReferences("couriervehicle", "crvh_group", numericToken(group.getId())));
        usages.put("Leistungen", countReferences("service", "srv_group", numericToken(group.getId())));
        usages.put("Leistungstypen", countReferences("servicetype", "srvt_group", numericToken(group.getId())));
        long otherUsages = countReferences("groups", "grp_group", numericToken(group.getId()))
                + countReferences("article", "at_group", numericToken(group.getId()))
                + countReferences("branch", "br_group", numericToken(group.getId()))
                + countReferences("metafieldcategory", "mtfc_group", numericToken(group.getId()))
                + countReferences("metafieldtemplate", "mtft_group", numericToken(group.getId()));
        if (otherUsages > 0) {
            usages.put("Weitere", otherUsages);
        }

        dto.setUsageCount(usages.values().stream().mapToLong(Long::longValue).sum());
        dto.setUsageSummary(usages.entrySet().stream()
                .filter(entry -> entry.getValue() > 0)
                .map(entry -> entry.getKey() + ": " + entry.getValue())
                .collect(Collectors.joining(", ")));
        if (dto.getUsageSummary() == null || dto.getUsageSummary().isBlank()) {
            dto.setUsageSummary("Keine Zuordnungen");
        }
        return dto;
    }

    private FilterAdminDto toFilterDto(CourierFilter filter) {
        FilterAdminDto dto = new FilterAdminDto();
        dto.setId(filter.getId());
        dto.setShortCode(filter.getShortCode());
        dto.setText(filter.getText());
        dto.setType(filter.getType());
        dto.setTypeLabel(filterTypeLabel(filter.getType()));
        dto.setStatus(filter.getStatus());
        dto.setStatusLabel(filterStatusLabel(filter.getStatus()));
        dto.setSort(filter.getSort() != null ? filter.getSort() : 0);
        dto.setLongText(filter.getLongText() != null ? filter.getLongText() : "");

        Map<String, Long> usages = new LinkedHashMap<>();
        usages.put("Kunden", countReferences("customer", "cs_filter", filterToken(filter.getShortCode())));
        usages.put("Kuriere", countReferences("courier", "cr_filter", filterToken(filter.getShortCode())));
        usages.put("Fahrzeuge", countReferences("couriervehicle", "crvh_filter", filterToken(filter.getShortCode())));
        dto.setUsageCount(usages.values().stream().mapToLong(Long::longValue).sum());
        dto.setUsageSummary(usages.entrySet().stream()
                .filter(entry -> entry.getValue() > 0)
                .map(entry -> entry.getKey() + ": " + entry.getValue())
                .collect(Collectors.joining(", ")));
        if (dto.getUsageSummary() == null || dto.getUsageSummary().isBlank()) {
            dto.setUsageSummary("Keine Zuordnungen");
        }
        return dto;
    }

    private AppGroup loadVisibleGroup(Long id, Long headquartersId, Long mandatorId) {
        AppGroup group = appGroupRepository.findById(Objects.requireNonNull(id))
                .orElseThrow(() -> new IllegalArgumentException("Gruppe nicht gefunden."));
        if (!Objects.equals(group.getMandatorId(), mandatorId)
                || (group.getHeadquartersId() != null
                && group.getHeadquartersId() != 0L
                && !Objects.equals(group.getHeadquartersId(), headquartersId))) {
            throw new IllegalArgumentException("Gruppe ist fuer diese Niederlassung nicht verfuegbar.");
        }
        return group;
    }

    private void ensureGroupNameUnique(Long mandatorId, String name, Long excludeId) {
        String normalizedName = name.toLowerCase(Locale.ROOT);
        boolean exists = appGroupRepository.findByMandatorIdOrderByNameAsc(mandatorId).stream()
                .anyMatch(existing -> !Objects.equals(existing.getId(), excludeId)
                        && existing.getName() != null
                        && existing.getName().trim().toLowerCase(Locale.ROOT).equals(normalizedName));
        if (exists) {
            throw new IllegalArgumentException("Der Gruppenname existiert bereits.");
        }
    }

    private void ensureFilterShortCodeUnique(String shortCode, Long excludeId) {
        String normalizedShortCode = shortCode.toLowerCase(Locale.ROOT);
        boolean exists = courierFilterRepository.findAll().stream()
                .anyMatch(existing -> !Objects.equals(existing.getId(), excludeId)
                        && existing.getShortCode() != null
                        && existing.getShortCode().trim().toLowerCase(Locale.ROOT).equals(normalizedShortCode));
        if (exists) {
            throw new IllegalArgumentException("Das Filterkuerzel existiert bereits.");
        }
    }

    private void ensureFilterTextUnique(String text, Long excludeId) {
        String normalizedText = text.toLowerCase(Locale.ROOT);
        boolean exists = courierFilterRepository.findAll().stream()
                .anyMatch(existing -> !Objects.equals(existing.getId(), excludeId)
                        && existing.getText() != null
                        && existing.getText().trim().toLowerCase(Locale.ROOT).equals(normalizedText));
        if (exists) {
            throw new IllegalArgumentException("Die Filterbezeichnung existiert bereits.");
        }
    }

    private void ensureFilterNotQueuedForDeletion(String shortCode) {
        String queue = parameterService.getParameterValue(ParameterKeys.FILTER_TO_BE_DELETED, 0L);
        String token = filterToken(shortCode);
        if (queue != null && !queue.isBlank() && queue.contains(token)) {
            throw new IllegalArgumentException("Dieses Filterkuerzel ist noch in der Legacy-Loeschqueue gesperrt.");
        }
    }

    private void queueFilterForDeletion(String shortCode) {
        String queue = parameterService.getParameterValue(ParameterKeys.FILTER_TO_BE_DELETED, 0L);
        String token = filterToken(shortCode);
        String normalizedQueue = normalizeDelimitedString(queue);
        if (!normalizedQueue.contains(token)) {
            normalizedQueue = normalizedQueue + shortCode + ",";
            parameterService.setParameterValue(ParameterKeys.FILTER_TO_BE_DELETED, normalizedQueue, 0L, 0L);
        }
    }

    private Long countReferences(String table, String field, String token) {
        Long count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM " + table + " WHERE " + field + " LIKE ?",
                Long.class,
                "%" + token + "%"
        );
        return count != null ? count : 0L;
    }

    private void removeTokenFromField(String table, String field, String token) {
        jdbcTemplate.update(
                "UPDATE " + table + " SET " + field + " = REPLACE(" + field + ", ?, ',') WHERE " + field + " LIKE ?",
                token,
                "%" + token + "%"
        );
        jdbcTemplate.update(
                "UPDATE " + table + " SET " + field + " = '' WHERE " + field + " = ','"
        );
    }

    private Long resolveMandatorId(Long headquartersId) {
        if (headquartersId == null || headquartersId <= 0) {
            throw new IllegalArgumentException("Ungueltige Niederlassung.");
        }
        try {
            return Objects.requireNonNull(jdbcTemplate.queryForObject(
                    "SELECT md_id FROM mandatorheadquarters WHERE hq_id = ? LIMIT 1",
                    Long.class,
                    headquartersId
            ));
        } catch (DataAccessException e) {
            throw new IllegalArgumentException("Kein Mandant fuer die Niederlassung gefunden.");
        }
    }

    private String requireName(String value, String label) {
        String normalized = value != null ? value.trim() : "";
        if (normalized.isBlank()) {
            throw new IllegalArgumentException(label + " fehlt.");
        }
        return normalized;
    }

    private String requireShortCode(String value) {
        String normalized = value != null ? value.trim().toUpperCase(Locale.ROOT) : "";
        if (normalized.isBlank()) {
            throw new IllegalArgumentException("Filterkuerzel fehlt.");
        }
        return normalized;
    }

    private int normalizeFilterType(Integer value) {
        int normalized = value != null ? value : 0;
        if (normalized != 0 && normalized != 1) {
            throw new IllegalArgumentException("Unbekannter Filtertyp.");
        }
        return normalized;
    }

    private int normalizeFilterStatus(Integer value) {
        int normalized = value != null ? value : 2;
        if (normalized != 1 && normalized != 2) {
            throw new IllegalArgumentException("Unbekannter Filterstatus.");
        }
        return normalized;
    }

    private boolean isFlagEnabled(String value) {
        return "1".equals(value);
    }

    private String numericToken(Long id) {
        return "," + id + ",";
    }

    private String filterToken(String shortCode) {
        return "," + shortCode + ",";
    }

    private String normalizeDelimitedString(String value) {
        String normalized = value != null ? value.trim() : "";
        if (normalized.isBlank()) {
            return ",";
        }
        if (!normalized.startsWith(",")) {
            normalized = "," + normalized;
        }
        if (!normalized.endsWith(",")) {
            normalized = normalized + ",";
        }
        return normalized;
    }

    private String filterTypeLabel(Integer type) {
        return Integer.valueOf(1).equals(type) ? "Transporteursfilter" : "Fahrzeugfilter";
    }

    private String filterStatusLabel(Integer status) {
        return Integer.valueOf(1).equals(status) ? "Pflichtfilter" : "Optional";
    }
}
