package com.smyhub.store.mengyastorebackendjava.service;

import com.smyhub.store.mengyastorebackendjava.dto.request.AdminProductRequest;
import com.smyhub.store.mengyastorebackendjava.dto.response.ProductResponse;
import com.smyhub.store.mengyastorebackendjava.entity.Product;
import com.smyhub.store.mengyastorebackendjava.entity.ProductCode;
import com.smyhub.store.mengyastorebackendjava.exception.BusinessException;
import com.smyhub.store.mengyastorebackendjava.repository.ProductCodeRepository;
import com.smyhub.store.mengyastorebackendjava.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Service
@RequiredArgsConstructor
public class ProductService {

    private static final String DEFAULT_COVER_URL = "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png";
    private static final int VIEW_COOLDOWN_HOURS = 6;
    private static final int MAX_SCREENSHOT_URLS = 5;

    private final ProductRepository productRepository;
    private final ProductCodeRepository productCodeRepository;
    private final Map<String, LocalDateTime> recentViews = new ConcurrentHashMap<>();

    public List<ProductResponse> listPublicProducts() {
        return productRepository.findByActiveTrueOrderByCreatedAtDesc().stream()
                .map(p -> toResponse(p, loadCodeCount(p), false, false))
                .toList();
    }

    public List<ProductResponse> listAllProducts() {
        return productRepository.findAllByOrderByCreatedAtDesc().stream()
                .map(p -> toResponse(p, loadCodeCount(p), true, true))
                .toList();
    }

    public ProductResponse getProductForAdmin(String id) {
        Product p = findById(id);
        List<String> codes = productCodeRepository.findByProductId(id).stream()
                .map(ProductCode::getCode).toList();
        return toResponse(p, (long) codes.size(), true, true, codes);
    }

    @Transactional
    public ProductResponse createProduct(AdminProductRequest req) {
        Product p = new Product();
        p.setId(UUID.randomUUID().toString());
        p.setCreatedAt(LocalDateTime.now());
        applyRequest(p, req);
        productRepository.save(p);
        replaceCodes(p.getId(), sanitizeCodes(req.getCodes()));
        return getProductForAdmin(p.getId());
    }

    @Transactional
    public ProductResponse updateProduct(String id, AdminProductRequest req) {
        Product p = findById(id);
        applyRequest(p, req);
        productRepository.save(p);
        replaceCodes(id, sanitizeCodes(req.getCodes()));
        return getProductForAdmin(id);
    }

    @Transactional
    public ProductResponse toggleStatus(String id, boolean active) {
        Product p = findById(id);
        p.setActive(active);
        productRepository.save(p);
        long count = productCodeRepository.countByProductId(id);
        return toResponse(p, count, true, false);
    }

    @Transactional
    public void deleteProduct(String id) {
        findById(id);
        productCodeRepository.deleteByProductId(id);
        productRepository.deleteById(id);
    }

    public ProductResponse recordView(String id, String fingerprint) {
        cleanupRecentViews();
        String key = buildViewKey(id, fingerprint);
        LocalDateTime last = recentViews.get(key);
        LocalDateTime now = LocalDateTime.now();
        boolean counted = false;
        if (last == null || last.plusHours(VIEW_COOLDOWN_HOURS).isBefore(now)) {
            productRepository.incrementViewCount(id);
            recentViews.put(key, now);
            counted = true;
        }
        Product p = findById(id);
        return ProductResponse.builder()
                .id(p.getId())
                .viewCount(p.getViewCount())
                .build();
    }

    @Transactional
    public void incrementSold(String id, int count) {
        productRepository.incrementTotalSold(id, count);
    }

    public Product findById(String id) {
        return productRepository.findById(id)
                .orElseThrow(() -> new BusinessException("商品不存在", HttpStatus.NOT_FOUND));
    }

    @Transactional
    public List<String> popCodes(String productId, int count) {
        List<ProductCode> codes = productCodeRepository.findByProductId(productId);
        if (codes.size() < count) {
            throw new BusinessException("卡密不足");
        }
        List<String> popped = codes.subList(0, count).stream().map(ProductCode::getCode).toList();
        codes.subList(0, count).forEach(c -> productCodeRepository.deleteById(c.getId()));
        return popped;
    }

    private long loadCodeCount(Product p) {
        if ("fixed".equalsIgnoreCase(p.getFulfillmentType())) {
            return -1;
        }
        return productCodeRepository.countByProductId(p.getId());
    }

    private void replaceCodes(String productId, List<String> codes) {
        productCodeRepository.deleteByProductId(productId);
        if (!codes.isEmpty()) {
            List<ProductCode> rows = codes.stream()
                    .map(c -> new ProductCode(productId, c)).toList();
            productCodeRepository.saveAll(rows);
        }
    }

    private void applyRequest(Product p, AdminProductRequest req) {
        p.setName(req.getName());
        p.setPrice(req.getPrice());
        double discount = req.getDiscountPrice();
        p.setDiscountPrice((discount > 0 && discount < req.getPrice()) ? discount : 0);
        p.setTags(sanitizeTags(req.getTags()));
        String cover = req.getCoverUrl();
        p.setCoverUrl(StringUtils.hasText(cover) ? cover : DEFAULT_COVER_URL);
        List<String> screenshots = req.getScreenshotUrls();
        if (screenshots != null && screenshots.size() > MAX_SCREENSHOT_URLS) {
            screenshots = screenshots.subList(0, MAX_SCREENSHOT_URLS);
        }
        p.setScreenshotUrls(screenshots != null ? screenshots : new ArrayList<>());
        p.setVerificationUrl(req.getVerificationUrl() != null ? req.getVerificationUrl().strip() : "");
        p.setDescription(req.getDescription());
        p.setActive(req.isActive());
        p.setRequireLogin(req.isRequireLogin());
        p.setMaxPerAccount(req.getMaxPerAccount());
        p.setDeliveryMode(StringUtils.hasText(req.getDeliveryMode()) ? req.getDeliveryMode() : "auto");
        String ft = normalizeFullfillmentType(req.getFulfillmentType());
        p.setFulfillmentType(ft);
        if ("fixed".equals(ft)) {
            p.setFixedContent(req.getFixedContent() != null ? req.getFixedContent().strip() : "");
        } else {
            p.setFixedContent("");
        }
        p.setShowNote(req.isShowNote());
        p.setShowContact(req.isShowContact());
    }

    private ProductResponse toResponse(Product p, long codeCount, boolean includeCodes, boolean includeFixedContent) {
        return toResponse(p, codeCount, includeCodes, includeFixedContent, null);
    }

    private ProductResponse toResponse(Product p, long codeCount, boolean includeCodes, boolean includeFixedContent, List<String> codes) {
        int qty = "fixed".equalsIgnoreCase(p.getFulfillmentType()) ? -1 : (int) codeCount;
        return ProductResponse.builder()
                .id(p.getId())
                .name(p.getName())
                .price(p.getPrice())
                .discountPrice(p.getDiscountPrice())
                .tags(p.getTags())
                .quantity(qty)
                .coverUrl(p.getCoverUrl())
                .screenshotUrls(p.getScreenshotUrls())
                .verificationUrl(p.getVerificationUrl())
                .codes(includeCodes ? codes : null)
                .viewCount(p.getViewCount())
                .description(p.getDescription())
                .active(p.isActive())
                .requireLogin(p.isRequireLogin())
                .maxPerAccount(p.getMaxPerAccount())
                .totalSold(p.getTotalSold())
                .deliveryMode(p.getDeliveryMode())
                .fulfillmentType(p.getFulfillmentType())
                .fixedContent(includeFixedContent ? p.getFixedContent() : null)
                .showNote(p.isShowNote())
                .showContact(p.isShowContact())
                .createdAt(p.getCreatedAt())
                .build();
    }

    private static String normalizeFullfillmentType(String t) {
        if (t != null && "fixed".equalsIgnoreCase(t.strip())) return "fixed";
        return "card";
    }

    private static List<String> sanitizeCodes(List<String> codes) {
        if (codes == null) return new ArrayList<>();
        Set<String> seen = new LinkedHashSet<>();
        for (String code : codes) {
            String trimmed = code != null ? code.strip() : "";
            if (!trimmed.isEmpty()) seen.add(trimmed);
        }
        return new ArrayList<>(seen);
    }

    private static List<String> sanitizeTags(List<String> tags) {
        if (tags == null) return new ArrayList<>();
        Set<String> seen = new LinkedHashSet<>();
        for (String tag : tags) {
            String t = tag != null ? tag.strip() : "";
            if (!t.isEmpty() && seen.size() < 20) seen.add(t);
        }
        return new ArrayList<>(seen);
    }

    private static String buildViewKey(String id, String fingerprint) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hash = md.digest((id + "|" + fingerprint).getBytes());
            StringBuilder hex = new StringBuilder();
            for (byte b : hash) hex.append(String.format("%02x", b));
            return hex.toString();
        } catch (Exception e) {
            return id + "|" + fingerprint;
        }
    }

    private void cleanupRecentViews() {
        LocalDateTime cutoff = LocalDateTime.now().minusHours(VIEW_COOLDOWN_HOURS);
        recentViews.entrySet().removeIf(e -> e.getValue().isBefore(cutoff));
    }
}
