package com.dedicatedcode.reitti.controller;

import com.dedicatedcode.reitti.dto.TimelineResponse;
import com.dedicatedcode.reitti.model.*;
import com.dedicatedcode.reitti.repository.*;
import com.dedicatedcode.reitti.service.*;

import java.util.Optional;
import com.dedicatedcode.reitti.service.processing.RawLocationPointProcessingTrigger;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.LocaleResolver;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;


@Controller
@RequestMapping("/settings")
public class SettingsController {

    private final ApiTokenService apiTokenService;
    private final UserJdbcService userJdbcService;
    private final QueueStatsService queueStatsService;
    private final PlaceService placeService;
    private final SignificantPlaceJdbcService placeJdbcService;
    private final ImportHandler importHandler;
    private final GeocodeServiceJdbcService geocodeServiceJdbcService;
    private final RawLocationPointProcessingTrigger rawLocationPointProcessingTrigger;
    private final ImmichIntegrationService immichIntegrationService;
    private final VisitJdbcService visitJdbcService;
    private final TripJdbcService tripJdbcService;
    private final ProcessedVisitJdbcService processedVisitJdbcService;
    private final RawLocationPointJdbcService rawLocationPointJdbcService;
    private final int maxErrors;
    private final boolean dataManagementEnabled;
    private final MessageSource messageSource;
    private final LocaleResolver localeResolver;
    private final Properties gitProperties = new Properties();
    private static final Logger logger = LoggerFactory.getLogger(SettingsController.class);

    public SettingsController(ApiTokenService apiTokenService,
                              UserJdbcService userJdbcService,
                              QueueStatsService queueStatsService,
                              PlaceService placeService, SignificantPlaceJdbcService placeJdbcService,
                              ImportHandler importHandler,
                              GeocodeServiceJdbcService geocodeServiceJdbcService,
                              RawLocationPointProcessingTrigger rawLocationPointProcessingTrigger,
                              ImmichIntegrationService immichIntegrationService,
                              VisitJdbcService visitJdbcService,
                              TripJdbcService tripJdbcService,
                              ProcessedVisitJdbcService processedVisitJdbcService,
                              RawLocationPointJdbcService rawLocationPointJdbcService,
                              @Value("${reitti.geocoding.max-errors}") int maxErrors,
                              @Value("${reitti.data-management.enabled:false}") boolean dataManagementEnabled,
                              MessageSource messageSource,
                              LocaleResolver localeResolver) {
        this.apiTokenService = apiTokenService;
        this.userJdbcService = userJdbcService;
        this.queueStatsService = queueStatsService;
        this.placeService = placeService;
        this.placeJdbcService = placeJdbcService;
        this.importHandler = importHandler;
        this.geocodeServiceJdbcService = geocodeServiceJdbcService;
        this.rawLocationPointProcessingTrigger = rawLocationPointProcessingTrigger;
        this.immichIntegrationService = immichIntegrationService;
        this.visitJdbcService = visitJdbcService;
        this.tripJdbcService = tripJdbcService;
        this.processedVisitJdbcService = processedVisitJdbcService;
        this.rawLocationPointJdbcService = rawLocationPointJdbcService;
        this.maxErrors = maxErrors;
        this.dataManagementEnabled = dataManagementEnabled;
        this.messageSource = messageSource;
        this.localeResolver = localeResolver;
        loadGitProperties();
    }

    private void loadGitProperties() {
        try (InputStream is = getClass().getClassLoader().getResourceAsStream("git.properties")) {
            if (is != null) {
                this.gitProperties.load(is);
                logger.info("git.properties loaded successfully.");
            } else {
                logger.warn("git.properties not found on classpath. About section may not display Git information.");
            }
        } catch (IOException e) {
            logger.error("Failed to load git.properties", e);
        }
    }

    private String getMessage(String key, Object... args) {
        return messageSource.getMessage(key, args, LocaleContextHolder.getLocale());
    }

    @GetMapping("/api-tokens-content")
    public String getApiTokensContent(Authentication authentication, Model model) {
        User currentUser = userJdbcService.getUserByUsername(authentication.getName());
        model.addAttribute("tokens", apiTokenService.getTokensForUser(currentUser));
        return "fragments/settings :: api-tokens-content";
    }

    @GetMapping("/users-content")
    public String getUsersContent(Authentication authentication, Model model) {
        String currentUsername = authentication.getName();
        List<User> users = userJdbcService.getAllUsers();
        model.addAttribute("users", users);
        model.addAttribute("currentUsername", currentUsername);
        return "fragments/settings :: users-content";
    }

    @GetMapping("/places-content")
    public String getPlacesContent(Authentication authentication,
                                   @RequestParam(defaultValue = "0") int page,
                                   Model model) {
        User currentUser = userJdbcService.getUserByUsername(authentication.getName());
        Page<SignificantPlace> placesPage = placeService.getPlacesForUser(currentUser, PageRequest.of(page, 20));

        // Convert to PlaceInfo objects
        List<TimelineResponse.PlaceInfo> places = placesPage.getContent().stream()
                .map(place -> new TimelineResponse.PlaceInfo(
                        place.getId(),
                        place.getName(),
                        place.getAddress(),
                        place.getCategory(),
                        place.getLatitudeCentroid(),
                        place.getLongitudeCentroid()
                ))
                .collect(Collectors.toList());

        // Add pagination info to model
        model.addAttribute("currentPage", placesPage.getNumber());
        model.addAttribute("totalPages", placesPage.getTotalPages());
        model.addAttribute("places", places);
        model.addAttribute("isEmpty", places.isEmpty());

        return "fragments/settings :: places-content";
    }

    @PostMapping("/tokens")
    public String createToken(Authentication authentication, @RequestParam String name, Model model) {
        User user = userJdbcService.getUserByUsername(authentication.getName());

        try {
            apiTokenService.createToken(user, name);
            model.addAttribute("successMessage", getMessage("message.success.token.created"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("message.error.token.creation", e.getMessage()));
        }

        // Get updated token list and add to model
        List<ApiToken> tokens = apiTokenService.getTokensForUser(user);
        model.addAttribute("tokens", tokens);

        // Return the api-tokens-content fragment
        return "fragments/settings :: api-tokens-content";
    }

    @PostMapping("/tokens/{tokenId}/delete")
    public String deleteToken(@PathVariable Long tokenId, Authentication authentication, Model model) {
        User user = userJdbcService.getUserByUsername(authentication.getName());

        try {
            apiTokenService.deleteToken(tokenId);
            model.addAttribute("successMessage", getMessage("message.success.token.deleted"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("message.error.token.deletion", e.getMessage()));
        }

        // Get updated token list and add to model
        List<ApiToken> tokens = apiTokenService.getTokensForUser(user);
        model.addAttribute("tokens", tokens);

        // Return the api-tokens-content fragment
        return "fragments/settings :: api-tokens-content";
    }

    @PostMapping("/users/{userId}/delete")
    public String deleteUser(@PathVariable Long userId, Authentication authentication, Model model) {
        String currentUsername = authentication.getName();
        User currentUser = userJdbcService.getUserByUsername(currentUsername);

        // Prevent self-deletion
        if (currentUser.getId().equals(userId)) {
            model.addAttribute("errorMessage", getMessage("message.error.user.self.delete"));
        } else {
            try {
                userJdbcService.deleteUser(userId);
                model.addAttribute("successMessage", getMessage("message.success.user.deleted"));
            } catch (Exception e) {
                model.addAttribute("errorMessage", getMessage("message.error.user.deletion", e.getMessage()));
            }
        }

        // Get updated user list and add to model
        List<User> users = userJdbcService.getAllUsers();
        model.addAttribute("users", users);
        model.addAttribute("currentUsername", currentUsername);

        // Return the users-content fragment
        return "fragments/settings :: users-content";
    }

    @PostMapping("/places/{placeId}/update")
    @ResponseBody
    public Map<String, Object> updatePlace(@PathVariable Long placeId,
                                           @RequestParam String name,
                                           Authentication authentication) {

        User user = (User) authentication.getPrincipal();
        Map<String, Object> response = new HashMap<>();
        if (this.placeJdbcService.exists(user, placeId)) {
            try {
                SignificantPlace significantPlace = placeJdbcService.findById(placeId).orElseThrow();
                placeJdbcService.update(significantPlace.withName(name));

                response.put("message", getMessage("message.success.place.updated"));
                response.put("success", true);
            } catch (Exception e) {
                response.put("message", getMessage("message.error.place.update", e.getMessage()));
                response.put("success", false);
            }
        } else {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN);
        }
        return response;
    }

    @PostMapping("/users")
    public String createUser(@RequestParam String username,
                             @RequestParam String displayName,
                             @RequestParam String password,
                             Authentication authentication,
                             Model model) {
        String currentUsername = authentication.getName();

        try {
            userJdbcService.createUser(username, displayName, password);
            model.addAttribute("successMessage", getMessage("message.success.user.created"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("message.error.user.creation", e.getMessage()));
        }

        // Get updated user list and add to model
        List<User> users = userJdbcService.getAllUsers();
        model.addAttribute("users", users);
        model.addAttribute("currentUsername", currentUsername);

        // Return the users-content fragment
        return "fragments/settings :: users-content";
    }

    @PostMapping("/users/update")
    public String updateUser(@RequestParam Long userId,
                             @RequestParam String username,
                             @RequestParam String displayName,
                             @RequestParam(required = false) String password,
                             Authentication authentication,
                             Model model) {
        String currentUsername = authentication.getName();
        User currentUser = userJdbcService.getUserById(userId);
        boolean isCurrentUser = currentUser.getId().equals(userId);

        try {
            userJdbcService.updateUser(userId, username, displayName, password);
            model.addAttribute("successMessage", getMessage("message.success.user.updated"));

            // If the current user was updated, update the authentication
            if (isCurrentUser && !currentUsername.equals(username)) {
                // We need to re-authenticate with the new username
                model.addAttribute("requireRelogin", true);
                model.addAttribute("newUsername", username);
            }
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("message.error.user.update", e.getMessage()));
        }

        // Get updated user list and add to model
        List<User> users = userJdbcService.getAllUsers();
        model.addAttribute("users", users);
        model.addAttribute("currentUsername", isCurrentUser ? username : currentUsername);

        // Return the users-content fragment
        return "fragments/settings :: users-content";
    }

    @GetMapping("/queue-stats-content")
    public String getQueueStatsContent(Model model) {
        model.addAttribute("queueStats", queueStatsService.getQueueStats());
        return "fragments/settings :: queue-stats-content";
    }

    @GetMapping("/file-upload-content")
    public String getDataImportContent() {
        return "fragments/settings :: file-upload-content";
    }

    @GetMapping("/language-content")
    public String getLanguageContent() {
        return "fragments/settings :: language-content";
    }

    @PostMapping("/language")
    public String changeLanguage(@RequestParam String lang, 
                                HttpServletRequest request, 
                                HttpServletResponse response, 
                                Model model) {
        try {
            Locale locale = Locale.forLanguageTag(lang);
            localeResolver.setLocale(request, response, locale);
            model.addAttribute("successMessage", getMessage("message.success.language.changed"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("message.error.language.change", e.getMessage()));
        }
        
        return "fragments/settings :: language-content";
    }

    @GetMapping("/integrations-content")
    public String getIntegrationsContent(Authentication authentication, Model model, HttpServletRequest request) {
        User currentUser = userJdbcService.getUserByUsername(authentication.getName());
        List<ApiToken> tokens = apiTokenService.getTokensForUser(currentUser);

        // Add the first token if available
        if (!tokens.isEmpty()) {
            model.addAttribute("firstToken", tokens.getFirst().getToken());
            model.addAttribute("hasToken", true);
        } else {
            model.addAttribute("hasToken", false);
        }

        // Build the server URL
        String scheme = request.getScheme();
        String serverName = request.getServerName();
        int serverPort = request.getServerPort();

        StringBuilder serverUrl = new StringBuilder();
        serverUrl.append(scheme).append("://").append(serverName);

        // Only add port if it's not the default port for the scheme
        if ((scheme.equals("http") && serverPort != 80) ||
                (scheme.equals("https") && serverPort != 443)) {
            serverUrl.append(":").append(serverPort);
        }

        model.addAttribute("serverUrl", serverUrl.toString());

        return "fragments/settings :: integrations-content";
    }

    @GetMapping("/photos-content")
    public String getPhotosContent(Authentication authentication, Model model) {
        User currentUser = userJdbcService.getUserByUsername(authentication.getName());
        Optional<ImmichIntegration> integration = immichIntegrationService.getIntegrationForUser(currentUser);
        
        if (integration.isPresent()) {
            model.addAttribute("immichIntegration", integration.get());
            model.addAttribute("hasIntegration", true);
        } else {
            model.addAttribute("hasIntegration", false);
        }
        
        return "fragments/settings :: photos-content";
    }

    @PostMapping("/immich-integration")
    public String saveImmichIntegration(@RequestParam String serverUrl,
                                       @RequestParam String apiToken,
                                       @RequestParam(defaultValue = "false") boolean enabled,
                                       Authentication authentication,
                                       Model model) {
        User currentUser = userJdbcService.getUserByUsername(authentication.getName());
        
        try {
            ImmichIntegration integration = immichIntegrationService.saveIntegration(
                currentUser, serverUrl, apiToken, enabled);
            
            model.addAttribute("immichIntegration", integration);
            model.addAttribute("hasIntegration", true);
            model.addAttribute("successMessage", getMessage("integrations.immich.config.saved"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("integrations.immich.config.error", e.getMessage()));
            // Re-populate form with submitted values
            ImmichIntegration tempIntegration = new ImmichIntegration(serverUrl, apiToken, enabled);
            model.addAttribute("immichIntegration", tempIntegration);
            model.addAttribute("hasIntegration", true);
        }
        
        return "fragments/settings :: photos-content";
    }

    @PostMapping("/immich-integration/test")
    @ResponseBody
    public Map<String, Object> testImmichConnection(@RequestParam String serverUrl,
                                                   @RequestParam String apiToken) {
        Map<String, Object> response = new HashMap<>();
        
        try {
            boolean connectionSuccessful = immichIntegrationService.testConnection(serverUrl, apiToken);
            
            if (connectionSuccessful) {
                response.put("success", true);
                response.put("message", getMessage("integrations.immich.connection.success"));
            } else {
                response.put("success", false);
                response.put("message", getMessage("integrations.immich.connection.failed", "Invalid configuration"));
            }
        } catch (Exception e) {
            response.put("success", false);
            response.put("message", getMessage("integrations.immich.connection.failed", e.getMessage()));
        }
        
        return response;
    }

    @GetMapping("/user-form")
    public String getUserForm(@RequestParam(required = false) Long userId,
                              @RequestParam(required = false) String username,
                              @RequestParam(required = false) String displayName,
                              Model model) {
        if (userId != null) {
            model.addAttribute("userId", userId);
            model.addAttribute("username", username);
            model.addAttribute("displayName", displayName);
        }
        return "fragments/settings :: user-form";
    }

    @PostMapping("/import/gpx")
    public String importGpx(@RequestParam("files") MultipartFile[] files,
                            Authentication authentication,
                            Model model) {
        User user = (User) authentication.getPrincipal();

        if (files.length == 0) {
            model.addAttribute("uploadErrorMessage", "No files selected");
            return "fragments/settings :: file-upload-content";
        }

        int totalProcessed = 0;
        int successCount = 0;
        StringBuilder errorMessages = new StringBuilder();

        for (MultipartFile file : files) {
            if (file.isEmpty() || file.getOriginalFilename() == null) {
                errorMessages.append("File ").append(file.getOriginalFilename()).append(" is empty. ");
                continue;
            }

            if (!file.getOriginalFilename().endsWith(".gpx")) {
                errorMessages.append("File ").append(file.getOriginalFilename()).append(" is not a GPX file. ");
                continue;
            }

            try (InputStream inputStream = file.getInputStream()) {
                Map<String, Object> result = importHandler.importGpx(inputStream, user);

                if ((Boolean) result.get("success")) {
                    totalProcessed += (Integer) result.get("pointsReceived");
                    successCount++;
                } else {
                    errorMessages.append("Error processing ").append(file.getOriginalFilename()).append(": ")
                            .append(result.get("error")).append(". ");
                }
            } catch (IOException e) {
                errorMessages.append("Error processing ").append(file.getOriginalFilename()).append(": ")
                        .append(e.getMessage()).append(". ");
            }
        }

        if (successCount > 0) {
            String message = "Successfully processed " + successCount + " file(s) with " + totalProcessed + " location points";
            if (!errorMessages.isEmpty()) {
                message += ". Errors: " + errorMessages;
            }
            model.addAttribute("uploadSuccessMessage", message);
            
            // Trigger processing pipeline for imported data
            try {
                rawLocationPointProcessingTrigger.start();
            } catch (Exception e) {
                logger.warn("Failed to trigger processing pipeline after GPX import", e);
            }
        } else {
            model.addAttribute("uploadErrorMessage", "No files were processed successfully. " + errorMessages);
        }

        return "fragments/settings :: file-upload-content";
    }

    @PostMapping("/import/google-takeout")
    public String importGoogleTakeout(@RequestParam("file") MultipartFile file,
                                      Authentication authentication,
                                      Model model) {
        User user = (User) authentication.getPrincipal();

        if (file.isEmpty() || file.getOriginalFilename() == null) {
            model.addAttribute("uploadErrorMessage", "File is empty");
            return "fragments/settings :: file-upload-content";
        }

        if (!file.getOriginalFilename().endsWith(".json")) {
            model.addAttribute("uploadErrorMessage", "Only JSON files are supported");
            return "fragments/settings :: file-upload-content";
        }

        try (InputStream inputStream = file.getInputStream()) {
            Map<String, Object> result = importHandler.importGoogleTakeout(inputStream, user);

            if ((Boolean) result.get("success")) {
                model.addAttribute("uploadSuccessMessage", result.get("message"));
                
                // Trigger processing pipeline for imported data
                try {
                    rawLocationPointProcessingTrigger.start();
                } catch (Exception e) {
                    logger.warn("Failed to trigger processing pipeline after Google Takeout import", e);
                }
            } else {
                model.addAttribute("uploadErrorMessage", result.get("error"));
            }

            return "fragments/settings :: file-upload-content";
        } catch (IOException e) {
            model.addAttribute("uploadErrorMessage", "Error processing file: " + e.getMessage());
            return "fragments/settings :: file-upload-content";
        }
    }

    @PostMapping("/import/geojson")
    public String importGeoJson(@RequestParam("files") MultipartFile[] files,
                                Authentication authentication,
                                Model model) {
        User user = (User) authentication.getPrincipal();

        if (files.length == 0) {
            model.addAttribute("uploadErrorMessage", "No files selected");
            return "fragments/settings :: file-upload-content";
        }

        int totalProcessed = 0;
        int successCount = 0;
        StringBuilder errorMessages = new StringBuilder();

        for (MultipartFile file : files) {
            if (file.isEmpty()) {
                errorMessages.append("File ").append(file.getOriginalFilename()).append(" is empty. ");
                continue;
            }

            String filename = file.getOriginalFilename();
            if (filename == null || (!filename.endsWith(".geojson") && !filename.endsWith(".json"))) {
                errorMessages.append("File ").append(filename).append(" is not a GeoJSON file. ");
                continue;
            }

            try (InputStream inputStream = file.getInputStream()) {
                Map<String, Object> result = importHandler.importGeoJson(inputStream, user);

                if ((Boolean) result.get("success")) {
                    totalProcessed += (Integer) result.get("pointsReceived");
                    successCount++;
                } else {
                    errorMessages.append("Error processing ").append(filename).append(": ")
                            .append(result.get("error")).append(". ");
                }
            } catch (IOException e) {
                errorMessages.append("Error processing ").append(filename).append(": ")
                        .append(e.getMessage()).append(". ");
            }
        }

        if (successCount > 0) {
            String message = "Successfully processed " + successCount + " file(s) with " + totalProcessed + " location points";
            if (!errorMessages.isEmpty()) {
                message += ". Errors: " + errorMessages;
            }
            model.addAttribute("uploadSuccessMessage", message);
            
            // Trigger processing pipeline for imported data
            try {
                rawLocationPointProcessingTrigger.start();
            } catch (Exception e) {
                logger.warn("Failed to trigger processing pipeline after GeoJSON import", e);
            }
        } else {
            model.addAttribute("uploadErrorMessage", "No files were processed successfully. " + errorMessages);
        }

        return "fragments/settings :: file-upload-content";
    }

    @GetMapping("/manage-data-content")
    public String getManageDataContent(Model model) {
        if (!dataManagementEnabled) {
            throw new RuntimeException("Data management is not enabled");
        }
        return "fragments/settings :: manage-data-content";
    }

    @PostMapping("/manage-data/process-visits-trips")
    public String processVisitsTrips(Model model) {
        if (!dataManagementEnabled) {
            throw new RuntimeException("Data management is not enabled");
        }

        try {
            rawLocationPointProcessingTrigger.start();
            model.addAttribute("successMessage", getMessage("data.process.success"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("data.process.error", e.getMessage()));
        }

        return "fragments/settings :: manage-data-content";
    }

    @PostMapping("/manage-data/clear-and-reprocess")
    public String clearAndReprocess(Authentication authentication, Model model) {
        if (!dataManagementEnabled) {
            throw new RuntimeException("Data management is not enabled");
        }

        try {
            User currentUser = userJdbcService.getUserByUsername(authentication.getName());
            
            // Clear all processed data except SignificantPlaces
            // This would need to be implemented in appropriate service classes
            // For now, we'll assume these methods exist or need to be created
            clearProcessedDataExceptPlaces(currentUser);
            
            // Mark all raw location points as unprocessed
            markRawLocationPointsAsUnprocessed(currentUser);
            
            // Trigger processing pipeline
            rawLocationPointProcessingTrigger.start();
            
            model.addAttribute("successMessage", getMessage("data.clear.reprocess.success"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("data.clear.reprocess.error", e.getMessage()));
        }

        return "fragments/settings :: manage-data-content";
    }

    private void clearProcessedDataExceptPlaces(User user) {
        // Clear all processed data except SignificantPlaces
        // Order matters due to foreign key constraints
        tripJdbcService.deleteAllForUser(user);
        processedVisitJdbcService.deleteAllForUser(user);
        visitJdbcService.deleteAllForUser(user);
    }

    private void markRawLocationPointsAsUnprocessed(User user) {
        // Mark all raw location points for the user as unprocessed
        rawLocationPointJdbcService.markAllAsUnprocessedForUser(user);
    }

    @GetMapping("/geocode-services-content")
    public String getGeocodeServicesContent(Model model) {
        model.addAttribute("geocodeServices", geocodeServiceJdbcService.findAllByOrderByNameAsc());
        model.addAttribute("maxErrors", maxErrors);
        return "fragments/settings :: geocode-services-content";
    }

    @PostMapping("/geocode-services")
    public String createGeocodeService(@RequestParam String name,
                                       @RequestParam String urlTemplate,
                                       Model model) {
        try {
            GeocodeService service = new GeocodeService(name, urlTemplate, true, 0, null, null);
            geocodeServiceJdbcService.save(service);
            model.addAttribute("successMessage", getMessage("message.success.geocode.created"));
        } catch (Exception e) {
            model.addAttribute("errorMessage", getMessage("message.error.geocode.creation", e.getMessage()));
        }

        model.addAttribute("geocodeServices", geocodeServiceJdbcService.findAllByOrderByNameAsc());
        model.addAttribute("maxErrors", maxErrors);
        return "fragments/settings :: geocode-services-content";
    }

    @PostMapping("/geocode-services/{id}/toggle")
    public String toggleGeocodeService(@PathVariable Long id, Model model) {
        GeocodeService service = geocodeServiceJdbcService.findById(id).orElseThrow();
        service = service.withEnabled(!service.isEnabled());
        if (service.isEnabled()) {
            service = service.resetErrorCount();
        }
        geocodeServiceJdbcService.save(service);
        model.addAttribute("geocodeServices", geocodeServiceJdbcService.findAllByOrderByNameAsc());
        model.addAttribute("maxErrors", maxErrors);
        return "fragments/settings :: geocode-services-content";
    }

    @PostMapping("/geocode-services/{id}/delete")
    public String deleteGeocodeService(@PathVariable Long id, Model model) {
        GeocodeService service = geocodeServiceJdbcService.findById(id).orElseThrow();
        geocodeServiceJdbcService.delete(service);
        model.addAttribute("geocodeServices", geocodeServiceJdbcService.findAllByOrderByNameAsc());
        model.addAttribute("maxErrors", maxErrors);
        return "fragments/settings :: geocode-services-content";
    }

    @PostMapping("/geocode-services/{id}/reset-errors")
    public String resetGeocodeServiceErrors(@PathVariable Long id, Model model) {
        GeocodeService service = geocodeServiceJdbcService.findById(id).orElseThrow();
        geocodeServiceJdbcService.save(service.resetErrorCount().withEnabled(true));
        model.addAttribute("geocodeServices", geocodeServiceJdbcService.findAllByOrderByNameAsc());
        model.addAttribute("maxErrors", maxErrors);
        return "fragments/settings :: geocode-services-content";
    }

    @GetMapping("/about-content")
    public String getAboutContent(Model model) {
        String notAvailable = getMessage("about.not.available");
        String property = gitProperties.getProperty("git.tags");
        if (!StringUtils.hasText(property)) {
            property = "development";
        }
        model.addAttribute("buildVersion", property);

        String commitId = gitProperties.getProperty("git.commit.id.abbrev", notAvailable);
        String commitTime = gitProperties.getProperty("git.commit.time");

        StringBuilder commitDetails = new StringBuilder();
        if (!commitId.equals(notAvailable)) {
            commitDetails.append(commitId);
            if (commitTime != null && !commitTime.isEmpty()) {
                commitDetails.append(" (").append(commitTime);
                commitDetails.append(")");
            }
        } else {
            commitDetails.append(notAvailable);
        }


        model.addAttribute("gitCommitDetails", commitDetails.toString());
        model.addAttribute("buildTime", gitProperties.getProperty("git.build.time", notAvailable));
        return "fragments/settings :: about-content";
    }
}
