package com.imprimelibros.erp.users;

import com.imprimelibros.erp.datatables.DataTablesResponse;
import com.imprimelibros.erp.i18n.TranslationService;
import com.imprimelibros.erp.users.validation.UserForm;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.context.MessageSource;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.imprimelibros.erp.datatables.DataTablesRequest;
import com.imprimelibros.erp.datatables.DataTablesParser;
import com.imprimelibros.erp.config.Sanitizer;
import com.imprimelibros.erp.datatables.DataTable;

import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.List;
import java.util.Locale;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
@PreAuthorize("hasRole('ADMIN') or hasRole('SUPERADMIN')")
@RequestMapping("/users")
public class UserController {

    private UserDao repo;
    private RoleDao roleRepo;
    private MessageSource messageSource;
    private Sanitizer sanitizer;
    private PasswordEncoder passwordEncoder;
    private TranslationService translationService;

    public UserController(UserDao repo, UserService userService, MessageSource messageSource, Sanitizer sanitizer,
            PasswordEncoder passwordEncoder, RoleDao roleRepo, TranslationService translationService) {
        this.repo = repo;
        this.messageSource = messageSource;
        this.sanitizer = sanitizer;
        this.roleRepo = roleRepo;
        this.passwordEncoder = passwordEncoder;
        this.translationService = translationService;
    }

    @GetMapping
    public String list(Model model, Authentication authentication, Locale locale) {

        List<String> keys = List.of(
            "usuarios.delete.title",
            "usuarios.delete.text",
            "usuarios.eliminar",
            "usuarios.delete.button",
            "app.yes",
            "app.cancelar",
            "usuarios.delete.ok.title",
            "usuarios.delete.ok.text"
            );

            Map<String, String> translations = translationService.getTranslations(locale, keys);
            model.addAttribute("languageBundle", translations);

        return "imprimelibros/users/users-list";
    }

    // IMPORTANTE: asegúrate de que el controller es @RestController O anota el
    // método con @ResponseBody.
    @GetMapping(value = "/datatable", produces = "application/json")
    @ResponseBody
    public DataTablesResponse<Map<String, Object>> datatable(HttpServletRequest request, Locale locale) {

        DataTablesRequest dt = DataTablesParser.from(request); //

        // OJO: en la whitelist mete solo columnas "reales" y escalares (no relaciones).
        // Si 'role' es relación, sácalo de aquí:
        List<String> searchable = List.of("fullName", "userName", "enabled", "rolesConcat"); // <- busca por roles de
                                                                                             // verdad
        List<String> orderable = List.of("id", "fullName", "userName", "enabled", "roleRank"); // <- permite ordenar por estas
                                                                                         // columnas

        Specification<User> base = (root, query, cb) -> cb.conjunction();
        long total = repo.count();

        return DataTable
                .of(repo, User.class, dt, searchable) // 'searchable' en DataTable.java
                // edita columnas "reales":
                .orderable(orderable)
                .edit("enabled", (User u) -> {
                    if (u.isEnabled()) {
                        return "<span class=\"badge bg-success\" >"
                                + messageSource.getMessage("usuarios.tabla.activo", null, locale) + "</span>";
                    } else {
                        return "<span class=\"badge bg-danger\" >"
                                + messageSource.getMessage("usuarios.tabla.inactivo", null, locale) + "</span>";
                    }
                })
                // acciones virtuales:
                .add("roles", (User u) -> u.getRoles().stream()
                        .map(Role::getName)
                        .map(String::toLowerCase)
                        .map(rol -> "<span class=\"badge bg-primary\">" +
                                messageSource.getMessage("usuarios.rol." + rol, null, locale) + "</span>")
                        .collect(Collectors.joining(" ")))
                .add("actions", (user) -> {
                    return "<div class=\"hstack gap-3 flex-wrap\">\n" +
                            "                    <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
                            + "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n" +
                            "                    <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
                            + "\" class=\"link-danger btn-delete-user fs-15\"><i class=\"user-delete ri-delete-bin-line\"></i></a>\n" +
                            "                </div>";
                })
                .where(base)
                // Filtros custom:
                .filter((builder, req) -> {
                    // f_enabled: 'true' | 'false' | ''
                    String fEnabled = Optional.ofNullable(req.raw.get("f_enabled")).orElse("").trim();
                    if (!fEnabled.isEmpty()) {
                        boolean enabledVal = Boolean.parseBoolean(fEnabled);
                        builder.add((root, q, cb) -> cb.equal(root.get("enabled"), enabledVal));
                    }

                    // f_role: 'USER' | 'ADMIN' | 'SUPERADMIN' | ''
                    String fRole = Optional.ofNullable(req.raw.get("f_role")).orElse("").trim();
                    if (!fRole.isEmpty()) {
                        builder.add((root, q, cb) -> {
                            // join a roles; marca la query como distinct para evitar duplicados
                            var r = root.join("roles", jakarta.persistence.criteria.JoinType.LEFT);
                            q.distinct(true);
                            return cb.equal(r.get("name"), fRole);
                        });
                    }
                })
                .toJson(total);
    }

    @GetMapping("form")
    public String getForm(@RequestParam(required = false) Long id,
            @ModelAttribute("user") UserForm form,
            BindingResult binding,
            Model model,
            HttpServletResponse response,
            Locale locale) {

        if (id != null) {
            var opt = repo.findById(id);
            if (opt.isEmpty()) {
                binding.reject("usuarios.error.noEncontrado",
                        messageSource.getMessage("usuarios.error.noEncontrado", null, locale));
                response.setStatus(404);
                model.addAttribute("action", "/users/" + id);
                return "imprimelibros/users/user-form :: userForm";
            }

            User u = opt.get();
            // map ENTIDAD -> DTO (¡no metas la entidad en "user"!)
            form.setId(u.getId());
            form.setFullName(u.getFullName());
            form.setUserName(u.getUserName());
            form.setEnabled(u.isEnabled());
            form.setRoleName(u.getRoles().stream().findFirst().map(Role::getName).orElse("USER"));
            form.setPassword(null);
            form.setConfirmPassword(null);

            model.addAttribute("action", "/users/" + id);
        } else {
            // Crear: valores por defecto
            form.setEnabled(true);
            model.addAttribute("action", "/users");
        }
        return "imprimelibros/users/user-form :: userForm";
    }

    @PostMapping
    public String create(
            @Validated(UserForm.Create.class) @ModelAttribute("user") UserForm form,
            BindingResult binding,
            Model model,
            HttpServletResponse response,
            Locale locale) {

        String normalized = sanitizer.plain(form.getUserName().trim());

        if (repo.existsByUserNameIgnoreCase(normalized)) {
            binding.rejectValue("userName", "validation.unique",
                    messageSource.getMessage("usuarios.error.duplicado", null, locale));
        }

        var optRole = roleRepo.findRoleByName(form.getRoleName());
        if (optRole.isEmpty()) {
            binding.rejectValue("roleName", "usuarios.errores.rol.invalido",
                    messageSource.getMessage("usuarios.error.rol", null, locale));
        }

        if (binding.hasErrors()) {
            response.setStatus(422); // <- clave
            model.addAttribute("action", "/users");
            return "imprimelibros/users/user-form :: userForm";
        }

        User u = new User();
        u.setFullName(sanitizer.plain(form.getFullName()));
        u.setUserName(normalized.toLowerCase());
        u.setPassword(passwordEncoder.encode(form.getPassword()));
        java.util.Set<Role> roles = new java.util.HashSet<>();
        roles.add(optRole.get());
        u.setRoles(roles);
        u.setEnabled(Boolean.TRUE.equals(form.getEnabled()));
        try {
            repo.save(u);
        } catch (org.springframework.dao.DataIntegrityViolationException ex) {
            // carrera contra otra inserción: vuelve como error de campo
            binding.rejectValue("userName", "validation.unique",
                    messageSource.getMessage("usuarios.error.duplicado", null, locale));
            response.setStatus(422);
            model.addAttribute("action", "/users");
            return "imprimelibros/users/user-form :: userForm";
        }

        response.setStatus(204);
        return null;
    }

    @PutMapping("/{id}")
    public String edit(
            @PathVariable Long id,
            @Validated(UserForm.Update.class) @ModelAttribute("user") UserForm form,
            BindingResult binding,
            Model model,
            HttpServletResponse response,
            Locale locale) {

        var uOpt = repo.findById(id);
        if (uOpt.isEmpty()) {
            binding.reject("usuarios.error.noEncontrado",
                    messageSource.getMessage("usuarios.error.noEncontrado", null, locale));
        }

        String normalized = sanitizer.plain(form.getUserName()).trim();
        if (repo.existsByUserNameIgnoreCaseAndIdNot(normalized, id)) {
            binding.rejectValue("userName", "validation.unique",
                    messageSource.getMessage("usuarios.error.duplicado", null, locale));
        }

        var optRole = roleRepo.findRoleByName(form.getRoleName());
        if (optRole.isEmpty()) {
            binding.rejectValue("roleName", "usuarios.errores.rol.invalido",
                    messageSource.getMessage("usuarios.error.rol", null, locale));
        }

        if (binding.hasErrors()) {
            response.setStatus(422);
            model.addAttribute("action", "/users/" + id);
            return "imprimelibros/users/user-form :: userForm";
        }

        var u = uOpt.get();
        u.setFullName(sanitizer.plain(form.getFullName()).trim());
        u.setUserName(normalized.toLowerCase());
        if (form.getPassword() != null && !form.getPassword().isBlank()) {
            u.setPassword(passwordEncoder.encode(form.getPassword()));
        }
        u.setRoles(new java.util.HashSet<>(java.util.List.of(optRole.get())));
        u.setEnabled(Boolean.TRUE.equals(form.getEnabled()));
        try {
            repo.save(u);
        } catch (org.springframework.dao.DataIntegrityViolationException ex) {
            binding.rejectValue("userName", "validation.unique",
                    messageSource.getMessage("usuarios.error.duplicado", null, locale));
            response.setStatus(422);
            model.addAttribute("action", "/users/" + id);
            return "imprimelibros/users/user-form :: userForm";
        }

        response.setStatus(204);
        return null;
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> delete(@PathVariable Long id, Authentication authentication, Locale locale) {
        return repo.findById(id).map(u -> {
            
            if (authentication != null && u.getUserName().equalsIgnoreCase(authentication.getName())) {
                return ResponseEntity.status(HttpStatus.FORBIDDEN)
                        .body(Map.of("message", messageSource.getMessage("usuarios.error.delete-self", null, locale)));
            }
            try {
                repo.delete(u);
                return ResponseEntity.status(HttpStatus.OK).body(
                    Map.of("message", messageSource.getMessage("usuarios.exito.eliminado", null, locale))
                );
            } catch (DataIntegrityViolationException dive) {
                // Restricción FK / dependencias
                return ResponseEntity.status(HttpStatus.CONFLICT)
                        .body(Map.of("message", messageSource.getMessage("usuarios.error.delete-relational-data", null, locale)));
            } catch (Exception ex) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                        .body(Map.of("message", messageSource.getMessage("usuarios.error.delete-internal-error", null, locale)));
            }
        }).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(Map.of("message", messageSource.getMessage("usuarios.error.delete-not-found", null, locale))));
    }

}
