package de.votian.services.service;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;

@Service
@Transactional
public class MetaFieldAdminService {

    private final JdbcTemplate jdbcTemplate;

    public MetaFieldAdminService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional(readOnly = true)
    public List<CategoryRow> findCategories(Long headquartersId) {
        requireHeadquarters(headquartersId);
        return jdbcTemplate.query(
                """
                        SELECT mtfc.mtfc_id,
                               mtfc.mtfc_mnemonic,
                               mtfc.mtfc_description,
                               mtfc.mtft_id,
                               COALESCE(mtft.mtft_name, '') AS mtft_name,
                               (
                                   SELECT COUNT(*)
                                   FROM metafieldcategorykey AS mtfck
                                   WHERE mtfck.mtfc_id = mtfc.mtfc_id
                                     AND mtfck.hq_id = ?
                               ) AS field_count
                        FROM metafieldcategory AS mtfc
                        LEFT JOIN metafieldtemplate AS mtft ON mtft.mtft_id = mtfc.mtft_id
                        ORDER BY mtfc.mtfc_description, mtfc.mtfc_id
                        """,
                (rs, rowNum) -> new CategoryRow(
                        rs.getLong("mtfc_id"),
                        rs.getString("mtfc_mnemonic"),
                        rs.getString("mtfc_description"),
                        nullableLong(rs.getObject("mtft_id")),
                        blankToEmpty(rs.getString("mtft_name")),
                        rs.getLong("field_count")
                ),
                headquartersId
        );
    }

    public CategoryRow createCategory(Long headquartersId, String mnemonic, String description, Long templateId) {
        requireHeadquarters(headquartersId);
        String normalizedMnemonic = requireText(mnemonic, "Kuerzel", 30);
        String normalizedDescription = requireText(description, "Bezeichnung", 100);
        Long normalizedTemplateId = normalizeTemplateId(templateId);

        ensureCategoryMnemonicUnique(normalizedMnemonic, null);
        ensureCategoryDescriptionUnique(normalizedDescription, null);

        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(connection -> {
            PreparedStatement statement = connection.prepareStatement(
                    """
                            INSERT INTO metafieldcategory (mtfc_mnemonic, mtfc_description, mtfc_group, mtft_id)
                            VALUES (?, ?, '', ?)
                            """,
                    Statement.RETURN_GENERATED_KEYS
            );
            statement.setString(1, normalizedMnemonic);
            statement.setString(2, normalizedDescription);
            if (normalizedTemplateId != null) {
                statement.setLong(3, normalizedTemplateId);
            } else {
                statement.setObject(3, null);
            }
            return statement;
        }, keyHolder);

        return getCategory(requireGeneratedId(keyHolder, "Kategorie"));
    }

    public CategoryRow updateCategory(Long id, Long headquartersId, String mnemonic, String description, Long templateId) {
        requireHeadquarters(headquartersId);
        requireCategoryExists(id);

        String normalizedMnemonic = requireText(mnemonic, "Kuerzel", 30);
        String normalizedDescription = requireText(description, "Bezeichnung", 100);
        Long normalizedTemplateId = normalizeTemplateId(templateId);

        ensureCategoryMnemonicUnique(normalizedMnemonic, id);
        ensureCategoryDescriptionUnique(normalizedDescription, id);

        jdbcTemplate.update(
                """
                        UPDATE metafieldcategory
                        SET mtfc_mnemonic = ?, mtfc_description = ?, mtft_id = ?
                        WHERE mtfc_id = ?
                        """,
                normalizedMnemonic,
                normalizedDescription,
                normalizedTemplateId,
                id
        );
        return getCategory(id);
    }

    @Transactional(readOnly = true)
    public List<FieldRow> findFields(String filter) {
        String normalizedFilter = filter != null ? filter.trim().toLowerCase(Locale.ROOT) : "";
        if (normalizedFilter.isBlank()) {
            return jdbcTemplate.query(
                    """
                            SELECT mtfk_id, mtfk_type, mtfk_name
                            FROM metafieldkey
                            ORDER BY mtfk_name, mtfk_id
                            """,
                    (rs, rowNum) -> new FieldRow(
                            rs.getLong("mtfk_id"),
                            blankToEmpty(rs.getString("mtfk_type")),
                            blankToEmpty(rs.getString("mtfk_name"))
                    )
            );
        }
        return jdbcTemplate.query(
                """
                        SELECT mtfk_id, mtfk_type, mtfk_name
                        FROM metafieldkey
                        WHERE LOWER(COALESCE(mtfk_name, '')) LIKE ?
                        ORDER BY mtfk_name, mtfk_id
                        """,
                (rs, rowNum) -> new FieldRow(
                        rs.getLong("mtfk_id"),
                        blankToEmpty(rs.getString("mtfk_type")),
                        blankToEmpty(rs.getString("mtfk_name"))
                ),
                normalizedFilter + "%"
        );
    }

    public FieldRow createField(String type, String name) {
        String normalizedType = requireText(type, "Feldtyp", 200);
        String normalizedName = requireText(name, "Feldname", 100);
        ensureFieldNameUnique(normalizedName, null);

        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(connection -> {
            PreparedStatement statement = connection.prepareStatement(
                    "INSERT INTO metafieldkey (mtfk_type, mtfk_name) VALUES (?, ?)",
                    Statement.RETURN_GENERATED_KEYS
            );
            statement.setString(1, normalizedType);
            statement.setString(2, normalizedName);
            return statement;
        }, keyHolder);

        return getField(requireGeneratedId(keyHolder, "Feld"));
    }

    public FieldRow updateField(Long id, String type, String name) {
        requireFieldExists(id);
        String normalizedType = requireText(type, "Feldtyp", 200);
        String normalizedName = requireText(name, "Feldname", 100);
        ensureFieldNameUnique(normalizedName, id);

        jdbcTemplate.update(
                "UPDATE metafieldkey SET mtfk_type = ?, mtfk_name = ? WHERE mtfk_id = ?",
                normalizedType,
                normalizedName,
                id
        );
        return getField(id);
    }

    @Transactional(readOnly = true)
    public List<TemplateRow> findTemplates() {
        return jdbcTemplate.query(
                """
                        SELECT mtft.mtft_id,
                               mtft.mtft_name,
                               COALESCE(mtft.mtft_content, '') AS mtft_content,
                               (
                                   SELECT COUNT(*)
                                   FROM metafieldcategory AS mtfc
                                   WHERE mtfc.mtft_id = mtft.mtft_id
                               ) AS usage_count
                        FROM metafieldtemplate AS mtft
                        ORDER BY mtft.mtft_name, mtft.mtft_id
                        """,
                (rs, rowNum) -> new TemplateRow(
                        rs.getLong("mtft_id"),
                        blankToEmpty(rs.getString("mtft_name")),
                        blankToEmpty(rs.getString("mtft_content")),
                        rs.getLong("usage_count")
                )
        );
    }

    public TemplateRow createTemplate(String name, String content) {
        String normalizedName = requireText(name, "Vorlagenname", 100);
        ensureTemplateNameUnique(normalizedName, null);

        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(connection -> {
            PreparedStatement statement = connection.prepareStatement(
                    """
                            INSERT INTO metafieldtemplate (mtft_name, mtft_group, mtft_content)
                            VALUES (?, '', ?)
                            """,
                    Statement.RETURN_GENERATED_KEYS
            );
            statement.setString(1, normalizedName);
            statement.setString(2, normalizeContent(content));
            return statement;
        }, keyHolder);

        return getTemplate(requireGeneratedId(keyHolder, "Vorlage"));
    }

    public TemplateRow updateTemplate(Long id, String name, String content) {
        requireTemplateExists(id);
        String normalizedName = requireText(name, "Vorlagenname", 100);
        ensureTemplateNameUnique(normalizedName, id);

        jdbcTemplate.update(
                """
                        UPDATE metafieldtemplate
                        SET mtft_name = ?, mtft_content = ?
                        WHERE mtft_id = ?
                        """,
                normalizedName,
                normalizeContent(content),
                id
        );
        return getTemplate(id);
    }

    @Transactional(readOnly = true)
    public List<CategoryFieldRow> findCategoryFields(Long categoryId, Long headquartersId) {
        requireCategoryExists(categoryId);
        requireHeadquarters(headquartersId);
        return jdbcTemplate.query(
                """
                        SELECT mtfck.mtfck_id,
                               mtfck.mtfk_id,
                               mtfck.mtfck_sort,
                               mtfk.mtfk_name,
                               mtfk.mtfk_type
                        FROM metafieldcategorykey AS mtfck
                        JOIN metafieldkey AS mtfk ON mtfk.mtfk_id = mtfck.mtfk_id
                        WHERE mtfck.mtfc_id = ?
                          AND mtfck.hq_id = ?
                        ORDER BY mtfck.mtfck_sort, mtfck.mtfck_id
                        """,
                (rs, rowNum) -> new CategoryFieldRow(
                        rs.getLong("mtfck_id"),
                        rs.getLong("mtfk_id"),
                        blankToEmpty(rs.getString("mtfk_name")),
                        blankToEmpty(rs.getString("mtfk_type")),
                        rs.getInt("mtfck_sort")
                ),
                categoryId,
                headquartersId
        );
    }

    public CategoryFieldRow addCategoryField(Long categoryId, Long headquartersId, Long fieldId) {
        requireCategoryExists(categoryId);
        requireHeadquarters(headquartersId);
        requireFieldExists(fieldId);

        Integer duplicateCount = jdbcTemplate.queryForObject(
                """
                        SELECT COUNT(*)
                        FROM metafieldcategorykey
                        WHERE mtfc_id = ? AND hq_id = ? AND mtfk_id = ?
                        """,
                Integer.class,
                categoryId,
                headquartersId,
                fieldId
        );
        if (duplicateCount != null && duplicateCount > 0) {
            throw new IllegalArgumentException("Dieses Feld ist dem Formular bereits zugeordnet.");
        }

        Integer nextSort = jdbcTemplate.queryForObject(
                """
                        SELECT COALESCE(MAX(mtfck_sort), 0) + 1
                        FROM metafieldcategorykey
                        WHERE mtfc_id = ? AND hq_id = ?
                        """,
                Integer.class,
                categoryId,
                headquartersId
        );

        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(connection -> {
            PreparedStatement statement = connection.prepareStatement(
                    """
                            INSERT INTO metafieldcategorykey (mtfc_id, mtfk_id, hq_id, mtfck_sort)
                            VALUES (?, ?, ?, ?)
                            """,
                    Statement.RETURN_GENERATED_KEYS
            );
            statement.setLong(1, categoryId);
            statement.setLong(2, fieldId);
            statement.setLong(3, headquartersId);
            statement.setInt(4, nextSort != null ? nextSort : 1);
            return statement;
        }, keyHolder);

        return getCategoryField(requireGeneratedId(keyHolder, "Formularfeld"));
    }

    public void deleteCategoryField(Long assignmentId, Long headquartersId) {
        requireHeadquarters(headquartersId);
        CategoryFieldLocation location = jdbcTemplate.query(
                """
                        SELECT mtfc_id, hq_id
                        FROM metafieldcategorykey
                        WHERE mtfck_id = ?
                        """,
                rs -> rs.next()
                        ? new CategoryFieldLocation(rs.getLong("mtfc_id"), rs.getLong("hq_id"))
                        : null,
                assignmentId
        );
        if (location == null) {
            throw new IllegalArgumentException("Formularfeld nicht gefunden.");
        }
        if (!Objects.equals(location.headquartersId(), headquartersId)) {
            throw new IllegalArgumentException("Formularfeld gehoert zu einer anderen Niederlassung.");
        }

        jdbcTemplate.update("DELETE FROM metafieldcategorykey WHERE mtfck_id = ?", assignmentId);
        normalizeCategoryFieldSorts(location.categoryId(), headquartersId);
    }

    public List<CategoryFieldRow> reorderCategoryFields(Long categoryId, Long headquartersId, List<Long> assignmentIds) {
        requireCategoryExists(categoryId);
        requireHeadquarters(headquartersId);
        if (assignmentIds == null || assignmentIds.isEmpty()) {
            throw new IllegalArgumentException("Es wurde keine Sortierung uebergeben.");
        }

        List<Long> currentIds = jdbcTemplate.query(
                """
                        SELECT mtfck_id
                        FROM metafieldcategorykey
                        WHERE mtfc_id = ? AND hq_id = ?
                        ORDER BY mtfck_sort, mtfck_id
                        """,
                (rs, rowNum) -> rs.getLong(1),
                categoryId,
                headquartersId
        );
        if (currentIds.size() != assignmentIds.size()) {
            throw new IllegalArgumentException("Die uebergebene Sortierung ist unvollstaendig.");
        }
        Set<Long> expected = new HashSet<>(currentIds);
        Set<Long> actual = new HashSet<>(assignmentIds);
        if (!expected.equals(actual)) {
            throw new IllegalArgumentException("Die uebergebene Sortierung passt nicht zum Formular.");
        }

        for (int index = 0; index < assignmentIds.size(); index++) {
            jdbcTemplate.update(
                    "UPDATE metafieldcategorykey SET mtfck_sort = ? WHERE mtfck_id = ?",
                    index + 1,
                    assignmentIds.get(index)
            );
        }
        return findCategoryFields(categoryId, headquartersId);
    }

    private CategoryRow getCategory(Long id) {
        return jdbcTemplate.query(
                """
                        SELECT mtfc.mtfc_id,
                               mtfc.mtfc_mnemonic,
                               mtfc.mtfc_description,
                               mtfc.mtft_id,
                               COALESCE(mtft.mtft_name, '') AS mtft_name
                        FROM metafieldcategory AS mtfc
                        LEFT JOIN metafieldtemplate AS mtft ON mtft.mtft_id = mtfc.mtft_id
                        WHERE mtfc.mtfc_id = ?
                        """,
                rs -> rs.next()
                        ? new CategoryRow(
                        rs.getLong("mtfc_id"),
                        rs.getString("mtfc_mnemonic"),
                        rs.getString("mtfc_description"),
                        nullableLong(rs.getObject("mtft_id")),
                        blankToEmpty(rs.getString("mtft_name")),
                        0L
                )
                        : null,
                id
        );
    }

    private FieldRow getField(Long id) {
        return jdbcTemplate.query(
                """
                        SELECT mtfk_id, mtfk_type, mtfk_name
                        FROM metafieldkey
                        WHERE mtfk_id = ?
                        """,
                rs -> rs.next()
                        ? new FieldRow(
                        rs.getLong("mtfk_id"),
                        blankToEmpty(rs.getString("mtfk_type")),
                        blankToEmpty(rs.getString("mtfk_name"))
                )
                        : null,
                id
        );
    }

    private TemplateRow getTemplate(Long id) {
        return jdbcTemplate.query(
                """
                        SELECT mtft_id, mtft_name, COALESCE(mtft_content, '') AS mtft_content
                        FROM metafieldtemplate
                        WHERE mtft_id = ?
                        """,
                rs -> rs.next()
                        ? new TemplateRow(
                        rs.getLong("mtft_id"),
                        blankToEmpty(rs.getString("mtft_name")),
                        blankToEmpty(rs.getString("mtft_content")),
                        0L
                )
                        : null,
                id
        );
    }

    private CategoryFieldRow getCategoryField(Long assignmentId) {
        return jdbcTemplate.query(
                """
                        SELECT mtfck.mtfck_id,
                               mtfck.mtfk_id,
                               mtfck.mtfck_sort,
                               mtfk.mtfk_name,
                               mtfk.mtfk_type
                        FROM metafieldcategorykey AS mtfck
                        JOIN metafieldkey AS mtfk ON mtfk.mtfk_id = mtfck.mtfk_id
                        WHERE mtfck.mtfck_id = ?
                        """,
                rs -> rs.next()
                        ? new CategoryFieldRow(
                        rs.getLong("mtfck_id"),
                        rs.getLong("mtfk_id"),
                        blankToEmpty(rs.getString("mtfk_name")),
                        blankToEmpty(rs.getString("mtfk_type")),
                        rs.getInt("mtfck_sort")
                )
                        : null,
                assignmentId
        );
    }

    private void ensureCategoryMnemonicUnique(String mnemonic, Long excludeId) {
        Integer count = jdbcTemplate.queryForObject(
                """
                        SELECT COUNT(*)
                        FROM metafieldcategory
                        WHERE LOWER(mtfc_mnemonic) = ?
                          AND (? IS NULL OR mtfc_id <> ?)
                        """,
                Integer.class,
                mnemonic.toLowerCase(Locale.ROOT),
                excludeId,
                excludeId
        );
        if (count != null && count > 0) {
            throw new IllegalArgumentException("Das eingegebene Kuerzel existiert bereits.");
        }
    }

    private void ensureCategoryDescriptionUnique(String description, Long excludeId) {
        Integer count = jdbcTemplate.queryForObject(
                """
                        SELECT COUNT(*)
                        FROM metafieldcategory
                        WHERE LOWER(mtfc_description) = ?
                          AND (? IS NULL OR mtfc_id <> ?)
                        """,
                Integer.class,
                description.toLowerCase(Locale.ROOT),
                excludeId,
                excludeId
        );
        if (count != null && count > 0) {
            throw new IllegalArgumentException("Die eingegebene Bezeichnung existiert bereits.");
        }
    }

    private void ensureFieldNameUnique(String name, Long excludeId) {
        Integer count = jdbcTemplate.queryForObject(
                """
                        SELECT COUNT(*)
                        FROM metafieldkey
                        WHERE LOWER(mtfk_name) = ?
                          AND (? IS NULL OR mtfk_id <> ?)
                        """,
                Integer.class,
                name.toLowerCase(Locale.ROOT),
                excludeId,
                excludeId
        );
        if (count != null && count > 0) {
            throw new IllegalArgumentException("Der eingegebene Feldname existiert bereits.");
        }
    }

    private void ensureTemplateNameUnique(String name, Long excludeId) {
        Integer count = jdbcTemplate.queryForObject(
                """
                        SELECT COUNT(*)
                        FROM metafieldtemplate
                        WHERE LOWER(mtft_name) = ?
                          AND (? IS NULL OR mtft_id <> ?)
                        """,
                Integer.class,
                name.toLowerCase(Locale.ROOT),
                excludeId,
                excludeId
        );
        if (count != null && count > 0) {
            throw new IllegalArgumentException("Der eingegebene Vorlagenname existiert bereits.");
        }
    }

    private Long normalizeTemplateId(Long templateId) {
        if (templateId == null || templateId <= 0) {
            return null;
        }
        requireTemplateExists(templateId);
        return templateId;
    }

    private void requireCategoryExists(Long id) {
        requireExists("metafieldcategory", "mtfc_id", id, "Formular");
    }

    private void requireFieldExists(Long id) {
        requireExists("metafieldkey", "mtfk_id", id, "Feld");
    }

    private void requireTemplateExists(Long id) {
        requireExists("metafieldtemplate", "mtft_id", id, "Vorlage");
    }

    private void requireExists(String table, String idColumn, Long id, String label) {
        if (id == null || id <= 0) {
            throw new IllegalArgumentException(label + " nicht gefunden.");
        }
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM " + table + " WHERE " + idColumn + " = ?",
                Integer.class,
                id
        );
        if (count == null || count == 0) {
            throw new IllegalArgumentException(label + " nicht gefunden.");
        }
    }

    private void normalizeCategoryFieldSorts(Long categoryId, Long headquartersId) {
        List<Long> ids = jdbcTemplate.query(
                """
                        SELECT mtfck_id
                        FROM metafieldcategorykey
                        WHERE mtfc_id = ? AND hq_id = ?
                        ORDER BY mtfck_sort, mtfck_id
                        """,
                (rs, rowNum) -> rs.getLong(1),
                categoryId,
                headquartersId
        );
        for (int index = 0; index < ids.size(); index++) {
            jdbcTemplate.update(
                    "UPDATE metafieldcategorykey SET mtfck_sort = ? WHERE mtfck_id = ?",
                    index + 1,
                    ids.get(index)
            );
        }
    }

    private long requireGeneratedId(KeyHolder keyHolder, String label) {
        Number key = keyHolder.getKey();
        if (key == null) {
            throw new IllegalStateException(label + " konnte nicht gespeichert werden.");
        }
        return key.longValue();
    }

    private void requireHeadquarters(Long headquartersId) {
        if (headquartersId == null || headquartersId <= 0) {
            throw new IllegalArgumentException("Niederlassung ungueltig.");
        }
    }

    private String requireText(String value, String label, int maxLength) {
        String normalized = value != null ? value.trim() : "";
        if (normalized.isBlank()) {
            throw new IllegalArgumentException(label + " darf nicht leer sein.");
        }
        if (normalized.length() > maxLength) {
            throw new IllegalArgumentException(label + " ist zu lang.");
        }
        return normalized;
    }

    private String normalizeContent(String content) {
        return content != null ? content.trim() : "";
    }

    private static Long nullableLong(Object value) {
        return value instanceof Number number ? number.longValue() : null;
    }

    private static String blankToEmpty(String value) {
        return value != null ? value : "";
    }

    public record CategoryRow(Long id,
                              String mnemonic,
                              String description,
                              Long templateId,
                              String templateName,
                              long fieldCount) {
    }

    public record FieldRow(Long id, String type, String name) {
    }

    public record TemplateRow(Long id, String name, String content, long usageCount) {
    }

    public record CategoryFieldRow(Long assignmentId,
                                   Long fieldId,
                                   String fieldName,
                                   String fieldType,
                                   int sort) {
    }

    private record CategoryFieldLocation(Long categoryId, Long headquartersId) {
    }
}
