package cn.edu.ecnu.stu.bookstore.service.impl;

import cn.edu.ecnu.stu.bookstore.component.AppException;
import cn.edu.ecnu.stu.bookstore.component.Constants;
import cn.edu.ecnu.stu.bookstore.config.RabbitMqConfig;
import cn.edu.ecnu.stu.bookstore.mapper.BookMapper;
import cn.edu.ecnu.stu.bookstore.mapper.OrderMapper;
import cn.edu.ecnu.stu.bookstore.mapper.StoreMapper;
import cn.edu.ecnu.stu.bookstore.mapper.UserMapper;
import cn.edu.ecnu.stu.bookstore.pojo.*;
import cn.edu.ecnu.stu.bookstore.pojo.vo.NewOrderVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class BuyerService {

    @Autowired
    private BookMapper bookMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StoreMapper storeMapper;

    @Autowired
    private PasswordEncoder encoder;

    @Autowired
    @Lazy
    private BuyerService buyerService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void newOrder(NewOrderVO newOrderVO) {
        List<Map<String, Object>> bookList = newOrderVO.getBooks();
        if(bookList.size() < 1)
            throw new AppException(Constants.CLIENT_ERROR, Constants.ORDER_IS_NULL_ERROR);
        HashMap<String, Integer> bookMap = new HashMap<>();
        for(Map<String, Object> map : bookList) {
            bookMap.put((String) map.get("id"), (Integer) map.get("count"));
        }
        String storeId = newOrderVO.getStoreId();
        Store store = storeMapper.getStoreById(storeId);
        if(store == null)
            throw new AppException(Constants.CLIENT_ERROR, Constants.STORE_NON_EXIST_ERROR);

        buyerService.insertOrder(store, bookMap, bookList);
    }

    @Transactional
    public void insertOrder(Store store, HashMap<String, Integer> bookMap, List<Map<String, Object>> bookList) {
        String storeId = store.getStoreId();
        List<Book> books = bookMapper.batchSelect(storeId, bookMap.keySet());
        if(books.size() != bookList.size())
            throw new AppException(Constants.CLIENT_ERROR, Constants.BOOK_ERROR);
        List<Map<String, Object>> list = books.stream().map(book -> {
            int count = bookMap.get(book.getBookId());
            if(count > book.getStockLevel())
                throw new AppException(Constants.CLIENT_ERROR, Constants.STOCK_LEVEL_ERROR);
            Map<String, Object> map = new HashMap<>();
            map.put("count", count);
            map.put("book", book);
            return map;
        }).collect(Collectors.toList());
        BigDecimal sum = new BigDecimal(0);
        for(Book book : books) {
            sum = sum.add(book.getPrice().multiply(BigDecimal.valueOf(bookMap.get(book.getBookId()))));
        }
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String orderId = UUID.randomUUID().toString();
        Order order = Order.builder().orderId(orderId).buyerId(user.getId()).storeId(storeId)
                        .price(sum).status(OrderStatus.WAIT_PAYMENT)
                        .build();
        orderMapper.insert(order);
        orderMapper.insertOrderBook(orderId, list);
        for (Map<String, Object> map : list) {
            Book book = (Book)map.get("book");
            bookMapper.minusStockLevel(book.getBookId(), (int)map.get("count"));
        }
        rabbitTemplate.convertAndSend(RabbitMqConfig.EXPIRED_ORDER_EXCHANGE, RabbitMqConfig.EXPIRED_ORDER_ROUTING_KEY,
                orderId, message -> {
                    message.getMessageProperties().setDelay(10000);
                    return message;
                });
    }

    public void payment(Integer userId, String orderId, String password) {
        User user = userMapper.selectOneById(userId);
        if(!encoder.matches(password, user.getPassword()))
            throw new AppException(Constants.CLIENT_ERROR, Constants.PASSWORD_ERROR);
        Order order = orderMapper.selectById(orderId);
        if(order == null)
            throw new AppException(Constants.CLIENT_ERROR, Constants.ORDER_NON_EXIST_ERROR);
        if(!order.getStatus().equals(OrderStatus.WAIT_PAYMENT))
            throw new AppException(Constants.CLIENT_ERROR, Constants.ORDER_HAS_PAID_ERROR);
        if(order.getPrice().compareTo(user.getBalance()) > 0)
            throw new AppException(Constants.CLIENT_ERROR, Constants.BALANCE_ERROR);
        buyerService.updateBalanceAndOrder(storeMapper.getStoreById(order.getStoreId()).getSellerId(), order);
    }

    @Transactional
    public void updateBalanceAndOrder(Integer sellerId, Order order) {
        orderMapper.updateOrderStatus(order.getOrderId(), OrderStatus.WAIT_SEND);
        BigDecimal price = order.getPrice();
        userMapper.minusBalance(order.getBuyerId(), price);
        userMapper.addBalance(sellerId, price);
    }

    public void addFunds(String username, String password, BigDecimal addValue) {
        User user = userMapper.selectOneByName(username);
        if(!encoder.matches(password, user.getPassword()))
            throw new AppException(Constants.CLIENT_ERROR, Constants.PASSWORD_ERROR);
        userMapper.addBalance(user.getId(), addValue);

    }

    public List<Order> getOrderList() {
        User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return orderMapper.select(user.getId(), OrderStatus.WAIT_PAYMENT);

    }


    public void takeDelivery(String orderId) {
        Order order = orderMapper.selectById(orderId);
        if(order == null)
            throw new AppException(Constants.CLIENT_ERROR, Constants.ORDER_NON_EXIST_ERROR);
        User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(!order.getBuyerId().equals(user.getId()))
            throw new AppException(Constants.CLIENT_ERROR, Constants.AUTHORITY_ERROR);
        if(!order.getStatus().equals(OrderStatus.WAIT_RECEIVE))
            throw new AppException(Constants.CLIENT_ERROR, Constants.STATUS_ERROR);
        orderMapper.updateOrderStatus(orderId, OrderStatus.COMPLETED);

    }
}
