package de.oaa.xxx.games.vanilla.controller;

import de.oaa.xxx.games.common.aufgaben.Toy;
import de.oaa.xxx.games.common.aufgaben.ToyPage;
import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity;
import de.oaa.xxx.games.common.entity.ToyEntity;
import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository;
import de.oaa.xxx.games.common.repository.GruppenAboRepository;
import de.oaa.xxx.games.common.repository.ToyRepository;
import de.oaa.xxx.subscription.SubscriptionLimitService;
import de.oaa.xxx.user.UserEntity;
import de.oaa.xxx.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

@RestController
@RequestMapping("/vanilla/toy")
@Transactional
public class VanillaToyController {

    private static final Logger LOGGER = LoggerFactory.getLogger(VanillaToyController.class);
    private static final int DEFAULT_PAGE_SIZE = 12;

    private final ToyRepository toyRepository;
    private final UserService userService;
    private final GruppenAboRepository aboRepository;
    private final AufgabenGruppeRepository gruppeRepository;
    private final SubscriptionLimitService limitService;

    public VanillaToyController(ToyRepository toyRepository,
                                 UserService userService,
                                 GruppenAboRepository aboRepository,
                                 AufgabenGruppeRepository gruppeRepository,
                                 SubscriptionLimitService limitService) {
        this.toyRepository = toyRepository;
        this.userService = userService;
        this.aboRepository = aboRepository;
        this.gruppeRepository = gruppeRepository;
        this.limitService = limitService;
    }

    @GetMapping("/list/user")
    public ResponseEntity<ToyPage> listUser(
            @RequestParam(name = "page", defaultValue = "0") int page,
            @RequestParam(name = "size", defaultValue = "" + DEFAULT_PAGE_SIZE) int size,
            Principal principal) {
        UserEntity user = userService.requireUser(principal);
        Page<ToyEntity> result = toyRepository.findByUserId(
                user.getUserId(), PageRequest.of(page, size, Sort.by("name")));
        return ResponseEntity.ok(toToyPage(result));
    }

    @GetMapping("/list/system")
    public ResponseEntity<ToyPage> listSystem(
            @RequestParam(name = "page", defaultValue = "0") int page,
            @RequestParam(name = "size", defaultValue = "" + DEFAULT_PAGE_SIZE) int size) {
        Page<ToyEntity> result = toyRepository.findByUserIdIsNull(
                PageRequest.of(page, size, Sort.by("name")));
        return ResponseEntity.ok(toToyPage(result));
    }

    /**
     * Returns all toys available to the current user for assignment to items:
     * own toys + system toys + toys referenced in subscribed vanilla-safe groups' items.
     */
    @GetMapping("/available")
    public ResponseEntity<List<Toy>> available(Principal principal) {
        UserEntity user = userService.requireUser(principal);

        List<ToyEntity> own    = toyRepository.findByUserId(user.getUserId(), PageRequest.of(0, 500, Sort.by("name"))).getContent();
        List<ToyEntity> system = toyRepository.findByUserIdIsNull(PageRequest.of(0, 500, Sort.by("name"))).getContent();

        Set<UUID> knownIds = new HashSet<>();
        own.forEach(t -> knownIds.add(t.getToyId()));
        system.forEach(t -> knownIds.add(t.getToyId()));

        Set<ToyEntity> fromAbos = new HashSet<>();
        aboRepository.findByUserId(user.getUserId()).forEach(abo -> {
            AufgabenGruppeEntity gruppe = abo.getAufgabenGruppe();
            // Only include toys from vanilla-safe groups (no Strafen, no Sperren)
            if (!gruppe.getStrafen().isEmpty() || !gruppe.getSperren().isEmpty()) return;
            gruppe.getAufgaben().forEach(a -> {
                if (a.getBenoetigteToys() != null)
                    a.getBenoetigteToys().stream().filter(t -> !knownIds.contains(t.getToyId())).forEach(fromAbos::add);
            });
        });

        List<Toy> result = new ArrayList<>();
        result.addAll(own.stream().map(ToyEntity::toToy).toList());
        result.addAll(system.stream().map(ToyEntity::toToy).toList());
        result.addAll(fromAbos.stream()
                .sorted(Comparator.comparing(ToyEntity::getName, String.CASE_INSENSITIVE_ORDER))
                .map(ToyEntity::toToy).toList());
        return ResponseEntity.ok(result);
    }

    /**
     * Returns all distinct toys required by aufgaben and finisher of the given gruppe IDs.
     * Only vanilla-safe groups (no Strafen, no Sperren) are considered.
     */
    @GetMapping("/required")
    public ResponseEntity<List<Toy>> required(@RequestParam List<UUID> gruppenIds) {
        Map<UUID, ToyEntity> toyMap = new java.util.LinkedHashMap<>();
        gruppeRepository.findAllById(gruppenIds).forEach(gruppe -> {
            gruppe.getAufgaben().forEach(a -> {
                if (a.getBenoetigteToys() != null)
                    a.getBenoetigteToys().forEach(t -> toyMap.putIfAbsent(t.getToyId(), t));
            });
            gruppe.getFinisher().forEach(f -> {
                if (f.getBenoetigteToys() != null)
                    f.getBenoetigteToys().forEach(t -> toyMap.putIfAbsent(t.getToyId(), t));
            });
        });
        List<Toy> result = toyMap.values().stream()
                .sorted(Comparator.comparing(ToyEntity::getName, String.CASE_INSENSITIVE_ORDER))
                .map(ToyEntity::toToy)
                .toList();
        return ResponseEntity.ok(result);
    }

    @GetMapping("/{toyId}")
    public ResponseEntity<Toy> get(@PathVariable("toyId") UUID toyId) {
        return toyRepository.findById(toyId)
                .map(entity -> ResponseEntity.ok(entity.toToy()))
                .orElse(ResponseEntity.noContent().build());
    }

    @PostMapping
    public ResponseEntity<Void> create(@RequestBody Toy toy, Principal principal) {
        if (toy.getName() == null || toy.getName().isBlank()) {
            return ResponseEntity.badRequest().build();
        }
        UserEntity user = userService.requireUser(principal);
        if (toyRepository.existsByNameIgnoreCaseAndUserIdIsNull(toy.getName())
                || toyRepository.existsByNameIgnoreCaseAndUserId(toy.getName(), user.getUserId())) {
            return ResponseEntity.status(409)
                    .header("X-Error", "duplicate-name")
                    .build();
        }
        if (toyRepository.countByUserId(user.getUserId()) >= limitService.maxToys(user.getUserId())) {
            return ResponseEntity.status(409)
                    .header("X-Error", "limit-reached")
                    .build();
        }
        ToyEntity entity = ToyEntity.create(toy);
        entity.setUserId(user.getUserId());
        toyRepository.save(entity);
        LOGGER.debug("User {} hat Vanilla-Toy {} '{}' erstellt", user.getUserId(), entity.getToyId(), entity.getName());
        return ResponseEntity.created(
                ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getToyId()).toUri()
        ).build();
    }

    @PostMapping("/copy/{toyId}")
    public ResponseEntity<Void> copy(@PathVariable("toyId") UUID toyId, Principal principal) {
        UserEntity user = userService.requireUser(principal);
        ToyEntity source = toyRepository.findById(toyId).orElse(null);
        if (source == null) {
            return ResponseEntity.notFound().build();
        }
        if (source.getUserId() != null) {
            return ResponseEntity.status(403).build();
        }
        if (toyRepository.existsByNameIgnoreCaseAndUserId(source.getName(), user.getUserId())) {
            return ResponseEntity.status(409)
                    .header("X-Error", "duplicate-name")
                    .build();
        }
        ToyEntity copy = new ToyEntity();
        copy.setToyId(UUID.randomUUID());
        copy.setName(source.getName());
        copy.setBeschreibung(source.getBeschreibung());
        copy.setUserId(user.getUserId());
        copy.setBild(source.getBild());
        toyRepository.save(copy);
        LOGGER.debug("User {} hat System-Toy {} kopiert für Vanilla (Kopie: {})", user.getUserId(), toyId, copy.getToyId());
        return ResponseEntity.status(201).build();
    }

    @PutMapping("/{toyId}")
    public ResponseEntity<Void> update(@PathVariable("toyId") UUID toyId, @RequestBody Toy toy, Principal principal) {
        if (toy.getName() == null || toy.getName().isBlank()) {
            return ResponseEntity.badRequest().build();
        }
        UserEntity user = userService.requireUser(principal);
        ToyEntity entity = toyRepository.findById(toyId).orElse(null);
        if (entity == null) {
            return ResponseEntity.notFound().build();
        }
        if (!user.getUserId().equals(entity.getUserId())) {
            return ResponseEntity.status(403).build();
        }
        if (toyRepository.existsByNameIgnoreCaseAndUserIdIsNullAndToyIdNot(toy.getName(), toyId)
                || toyRepository.existsByNameIgnoreCaseAndUserIdAndToyIdNot(toy.getName(), user.getUserId(), toyId)) {
            return ResponseEntity.status(409)
                    .header("X-Error", "duplicate-name")
                    .build();
        }
        entity.setName(toy.getName().trim());
        entity.setBeschreibung(toy.getBeschreibung());
        if (toy.getBild() != null) {
            entity.setBild(Base64.getDecoder().decode(toy.getBild()));
        }
        toyRepository.save(entity);
        LOGGER.debug("User {} hat Vanilla-Toy {} aktualisiert", user.getUserId(), toyId);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/{toyId}")
    public ResponseEntity<Void> delete(@PathVariable("toyId") UUID toyId, Principal principal) {
        UserEntity user = userService.requireUser(principal);
        ToyEntity toy = toyRepository.findById(toyId).orElse(null);
        if (toy == null) {
            return ResponseEntity.noContent().build();
        }
        if (!user.getUserId().equals(toy.getUserId())) {
            return ResponseEntity.status(403).build();
        }
        if (toyRepository.countAufgabeUsage(toyId) > 0
                || toyRepository.countStrafeUsage(toyId) > 0
                || toyRepository.countSperreUsage(toyId) > 0) {
            return ResponseEntity.status(409).build();
        }
        try {
            toyRepository.delete(toy);
            return ResponseEntity.accepted().build();
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            return ResponseEntity.internalServerError().build();
        }
    }

    private ToyPage toToyPage(Page<ToyEntity> page) {
        ToyPage toyPage = new ToyPage();
        toyPage.setContent(page.getContent().stream().map(ToyEntity::toToy).toList());
        toyPage.setCurrentPage(page.getNumber());
        toyPage.setTotalPages(page.getTotalPages());
        toyPage.setTotalElements(page.getTotalElements());
        return toyPage;
    }
}
