package com.fuint.common.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuint.common.dto.BookDto;
import com.fuint.common.dto.DayDto;
import com.fuint.common.dto.TimeDto;
import com.fuint.common.enums.StatusEnum;
import com.fuint.common.param.BookPage;
import com.fuint.common.param.BookableParam;
import com.fuint.common.service.BookService;
import com.fuint.common.service.SettingService;
import com.fuint.common.service.StoreService;
import com.fuint.common.util.DateUtil;
import com.fuint.framework.annoation.OperationServiceLog;
import com.fuint.framework.exception.BusinessCheckException;
import com.fuint.framework.pagination.PaginationResponse;
import com.fuint.repository.mapper.MtBookItemMapper;
import com.fuint.repository.mapper.MtBookMapper;
import com.fuint.repository.model.MtBanner;
import com.fuint.repository.model.MtBook;
import com.fuint.repository.model.MtBookItem;
import com.fuint.repository.model.MtStore;
import com.fuint.utils.StringUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.AllArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 预约服务接口
 *
 * Created by FSQ
 * CopyRight https://www.fuint.cn
 */
@Service
@AllArgsConstructor(onConstructor_= {@Lazy})
public class BookServiceImpl extends ServiceImpl<MtBookMapper, MtBook> implements BookService {

    private static final Logger logger = LoggerFactory.getLogger(BookServiceImpl.class);

    private MtBookMapper mtBookMapper;

    private MtBookItemMapper mtBookItemMapper;

    /**
     * 系统设置服务接口
     * */
    private SettingService settingService;

    /**
     * 店铺接口
     */
    private StoreService storeService;

    private static class BookingPriceRule {
        private final BigDecimal hours;
        private final BigDecimal price;

        private BookingPriceRule(BigDecimal hours, BigDecimal price) {
            this.hours = hours;
            this.price = price;
        }
    }

    /**
     * 分页查询预约列表
     *
     * @param bookPage
     * @return
     */
    @Override
    public PaginationResponse<BookDto> queryBookListByPagination(BookPage bookPage) {
        Page<MtBanner> pageHelper = PageHelper.startPage(bookPage.getPage(), bookPage.getPageSize());
        LambdaQueryWrapper<MtBook> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.ne(MtBook::getStatus, StatusEnum.DISABLE.getKey());

        String name = bookPage.getName();
        if (StringUtils.isNotBlank(name)) {
            lambdaQueryWrapper.like(MtBook::getName, name);
        }
        Integer cateId = bookPage.getCateId();
        if (cateId != null && cateId > 0) {
            lambdaQueryWrapper.like(MtBook::getCateId, cateId);
        }
        if (StringUtils.isNotBlank(bookPage.getStatus())) {
            lambdaQueryWrapper.eq(MtBook::getStatus, bookPage.getStatus());
        }
        Integer merchantId = bookPage.getMerchantId();
        if (merchantId != null && merchantId > 0) {
            lambdaQueryWrapper.eq(MtBook::getMerchantId, merchantId);
        }
        if (bookPage.getStoreId() != null) {
            lambdaQueryWrapper.and(wq -> wq
                    .eq(MtBook::getStoreId, 0)
                    .or()
                    .eq(MtBook::getStoreId, bookPage.getStoreId()));
        }

        lambdaQueryWrapper.orderByAsc(MtBook::getSort);
        List<MtBook> bookList = mtBookMapper.selectList(lambdaQueryWrapper);
        List<BookDto> dataList = new ArrayList<>();
        String baseImage = settingService.getUploadBasePath();
        if (bookList != null && bookList.size() > 0) {
            for (MtBook mtBook : bookList) {
                 BookDto bookDto = new BookDto();
                 BeanUtils.copyProperties(mtBook, bookDto);
                 bookDto.setLogo(baseImage + mtBook.getLogo());
                 dataList.add(bookDto);
            }
        }

        PageRequest pageRequest = PageRequest.of(bookPage.getPage(), bookPage.getPageSize());
        PageImpl pageImpl = new PageImpl(dataList, pageRequest, pageHelper.getTotal());
        PaginationResponse<BookDto> paginationResponse = new PaginationResponse(pageImpl, BookDto.class);
        paginationResponse.setTotalPages(pageHelper.getPages());
        paginationResponse.setTotalElements(pageHelper.getTotal());
        paginationResponse.setContent(dataList);

        return paginationResponse;
    }

    /**
     * 添加预约项目
     *
     * @param mtBook 预约信息
     * @return
     */
    @Override
    @OperationServiceLog(description = "添加预约项目")
    public MtBook addBook(MtBook mtBook) throws BusinessCheckException {
        Integer storeId = mtBook.getStoreId() == null ? 0 : mtBook.getStoreId();
        if (mtBook.getMerchantId() == null || mtBook.getMerchantId() <= 0) {
            MtStore mtStore = storeService.queryStoreById(storeId);
            if (mtStore != null) {
                mtBook.setMerchantId(mtStore.getMerchantId());
            }
        }
        if (mtBook.getMerchantId() == null || mtBook.getMerchantId() <= 0) {
            throw new BusinessCheckException("新增预约失败：所属商户不能为空！");
        }
        if (StringUtil.isEmpty(mtBook.getName())) {
            throw new BusinessCheckException("新增预约失败：项目名称不能为空！");
        }
        if (StringUtil.isEmpty(mtBook.getLogo())) {
            throw new BusinessCheckException("新增预约失败：封面图片不能为空！");
        }
        mtBook.setStoreId(storeId);
        mtBook.setStatus(StatusEnum.ENABLED.getKey());
        mtBook.setUpdateTime(new Date());
        mtBook.setCreateTime(new Date());
        Integer id = mtBookMapper.insert(mtBook);
        if (id > 0) {
            return mtBook;
        } else {
            logger.error("新增预约失败.");
            throw new BusinessCheckException("抱歉，新增预约失败！");
        }
    }

    /**
     * 根据ID获取预约项目信息
     *
     * @param id 预约项目ID
     * @param fillDate 填充日期
     * @return
     */
    @Override
    public BookDto getBookById(Integer id, boolean fillDate) throws ParseException {
        BookDto bookDto = new BookDto();
        MtBook mtBook = mtBookMapper.selectById(id);
        if (mtBook == null) {
            return null;
        }
        BeanUtils.copyProperties(mtBook, bookDto);

        List<DayDto> dateList = new ArrayList<>();
        String serviceDates = mtBook.getServiceDates();

        // 未填写日期，则未来7天都可以预约
        if (StringUtil.isEmpty(serviceDates)) {
            List<String> dates = new ArrayList<>();
            LocalDate today = LocalDate.now();
            for (int i = 0; i < 7; i++) {
                 LocalDate date = today.plusDays(i + 1);
                 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                 String dateString = date.format(formatter);
                 dates.add(dateString);
            }
            serviceDates = String.join(",", dates);
            if (fillDate) {
                bookDto.setServiceDates(serviceDates);
            }
        }

        if (StringUtil.isNotEmpty(serviceDates)) {
            List<String> dates = Arrays.asList(serviceDates.split(",").clone());
            if (dates.size() > 0) {
                for (String date : dates) {
                    Date currentDate = DateUtil.parseDate(date + " 23:59:59");
                    Date now = new Date();
                    SimpleDateFormat format = new SimpleDateFormat("EEEE", Locale.CHINA);
                    String week = format.format(currentDate);
                    DayDto day = new DayDto();
                    day.setWeek(week);
                    day.setDate(DateUtil.formatDate(currentDate, "MM-dd"));
                    if (now.compareTo(currentDate) <= 0) {
                        day.setEnable(true);
                    } else {
                        day.setEnable(false);
                    }
                    dateList.add(day);
                }
            }
        }
        bookDto.setDateList(dateList);

        List<TimeDto> timeList = new ArrayList<>();
        String serviceTimes = mtBook.getServiceTimes();

        // 未填写时段，则未来
        if (StringUtil.isEmpty(serviceTimes)) {
            serviceTimes = "08:30-12:00-1,14:00-18:00-1";
        }

        if (StringUtil.isNotEmpty(serviceTimes) && bookDto.getDateList().size() > 0) {
            List<String> times = Arrays.asList(serviceTimes.split(",").clone());
            if (times.size() == 1) {
                String[] range = times.get(0).split("-");
                if (range.length >= 2) {
                    String startTime = range[0];
                    String endTime = range[1];
                    int startMinutes = 0;
                    int endMinutes = 0;
                    if (startTime.contains(":")) {
                        String[] arr = startTime.split(":");
                        startMinutes = Integer.parseInt(arr[0]) * 60 + Integer.parseInt(arr[1]);
                    }
                    if (endTime.contains(":")) {
                        String[] arr = endTime.split(":");
                        endMinutes = Integer.parseInt(arr[0]) * 60 + Integer.parseInt(arr[1]);
                    }
                    for (int t = startMinutes; t + 60 <= endMinutes; t += 60) {
                        int h = t / 60;
                        int m = t % 60;
                        String timeStr = String.format("%02d:%02d", h, m);
                        TimeDto timeDto = new TimeDto();
                        timeDto.setTime(timeStr);
                        timeDto.setEnable(true);
                        timeList.add(timeDto);
                    }
                }
            } else if (times.size() > 0) {
                for (String time : times) {
                     TimeDto timeDto = new TimeDto();
                     String arr[] = time.split("-");
                     timeDto.setTime(arr[0] + "-" + arr[1]);
                     timeDto.setEnable(true);
                     timeList.add(timeDto);
                }
            }
        }
        bookDto.setTimeList(timeList);

        return bookDto;
    }

    /**
     * 修改预约项目
     *
     * @param  mtBook
     * @throws BusinessCheckException
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @OperationServiceLog(description = "修改预约项目")
    public MtBook updateBook(MtBook mtBook) throws BusinessCheckException {
        MtBook book = mtBookMapper.selectById(mtBook.getId());
        if (book == null) {
            throw new BusinessCheckException("该预约项目状态异常");
        }
        if (mtBook.getLogo() != null) {
            book.setLogo(mtBook.getLogo());
        }
        if (mtBook.getCateId() != null) {
            book.setCateId(mtBook.getCateId());
        }
        if (book.getName() != null) {
            book.setName(mtBook.getName());
        }
        if (mtBook.getStoreId() != null) {
            book.setStoreId(mtBook.getStoreId());
        }
        if (mtBook.getDescription() != null) {
            book.setDescription(mtBook.getDescription());
        }
        if (mtBook.getOperator() != null) {
            book.setOperator(mtBook.getOperator());
        }
        if (mtBook.getStatus() != null) {
            book.setStatus(mtBook.getStatus());
        }
        if (mtBook.getGoodsId() != null) {
            book.setGoodsId(mtBook.getGoodsId());
        }
        if (mtBook.getSort() != null) {
            book.setSort(mtBook.getSort());
        }
        if (mtBook.getServiceDates() != null) {
            book.setServiceDates(mtBook.getServiceDates());
        }
        if (mtBook.getServiceTimes() != null) {
            book.setServiceTimes(mtBook.getServiceTimes());
        }
        if (mtBook.getServiceStaffIds() != null) {
            book.setServiceStaffIds(mtBook.getServiceStaffIds());
        }
        if (mtBook.getPriceRules() != null) {
            book.setPriceRules(mtBook.getPriceRules());
        }
        book.setUpdateTime(new Date());
        mtBookMapper.updateById(book);
        return book;
    }

    /**
     * 是否可预约
     *
     * @param  param
     * @throws BusinessCheckException
     * @return
     * */
    @Override
    public List<String> isBookable(BookableParam param) throws BusinessCheckException, ParseException {
       MtBook mtBook = mtBookMapper.selectById(param.getBookId());
       List<String> result = new ArrayList<>();
       if (mtBook == null) {
           throw new BusinessCheckException("预约项目不存在");
       }

       List<String> bookList = new ArrayList<>();
       Integer limit = 0;
       String serviceTime = mtBook.getServiceTimes();

       if (StringUtil.isEmpty(serviceTime)) {
           serviceTime = "08:30-12:00-1";
       }

       String[] ranges = serviceTime.split(",");
       String[] rangeArr = ranges[0].split("-");
       String rangeStart = rangeArr.length > 0 ? rangeArr[0] : "";
       String rangeEnd = rangeArr.length > 1 ? rangeArr[1] : "";
       if (rangeArr.length > 2) {
           limit = Integer.parseInt(rangeArr[2]);
       } else {
           limit = 1;
       }

       Date now = new Date();
       if (StringUtil.isNotEmpty(param.getTime())) {
           if (StringUtil.isNotEmpty(param.getDate())) {
               bookList = mtBookItemMapper.getBookList(param.getBookId(), param.getDate(), param.getTime());
           }
           Integer bookNum = bookList.size();
           String[] arr = param.getTime().split("-");
           if (arr.length >= 2 && bookNum < limit) {
               String startTime = arr[0];
               String endTime = arr[1];
               String dateTime = param.getDate() + " " + endTime + ":00";
               Date currentDate = DateUtil.parseDate(dateTime);
               if (now.compareTo(currentDate) < 0) {
                   if (StringUtil.isNotEmpty(rangeStart) && StringUtil.isNotEmpty(rangeEnd)) {
                       int startM = Integer.parseInt(startTime.split(":")[0]) * 60 + Integer.parseInt(startTime.split(":")[1]);
                       int endM = Integer.parseInt(endTime.split(":")[0]) * 60 + Integer.parseInt(endTime.split(":")[1]);
                       int rangeStartM = Integer.parseInt(rangeStart.split(":")[0]) * 60 + Integer.parseInt(rangeStart.split(":")[1]);
                       int rangeEndM = Integer.parseInt(rangeEnd.split(":")[0]) * 60 + Integer.parseInt(rangeEnd.split(":")[1]);
                       if (startM >= rangeStartM && endM <= rangeEndM) {
                           result.add(param.getTime());
                       }
                   } else {
                       result.add(param.getTime());
                   }
               }
           }
       } else {
           if (StringUtil.isNotEmpty(rangeStart) && StringUtil.isNotEmpty(rangeEnd)) {
               int startM = Integer.parseInt(rangeStart.split(":")[0]) * 60 + Integer.parseInt(rangeStart.split(":")[1]);
               int endM = Integer.parseInt(rangeEnd.split(":")[0]) * 60 + Integer.parseInt(rangeEnd.split(":")[1]);
               for (int t = startM; t + 60 <= endM; t += 60) {
                   int h = t / 60;
                   int m = t % 60;
                   String timeStr = String.format("%02d:%02d", h, m);
                   String dateTime = param.getDate() + " " + timeStr + ":00";
                   Date currentDate = DateUtil.parseDate(dateTime);
                   if (now.compareTo(currentDate) < 0) {
                       result.add(timeStr);
                   }
               }
           }
       }
       return result;
    }

    /**
     * 获取可预约时段剩余名额
     */
    @Override
    public List<Map<String, Object>> getBookableRemain(BookableParam param) throws BusinessCheckException, ParseException {
        MtBook mtBook = mtBookMapper.selectById(param.getBookId());
        if (mtBook == null) {
            throw new BusinessCheckException("预约项目不存在");
        }
        List<Map<String, Object>> result = new ArrayList<>();
        if (StringUtil.isEmpty(param.getTime())) {
            return result;
        }

        String serviceTime = mtBook.getServiceTimes();
        if (StringUtil.isEmpty(serviceTime)) {
            serviceTime = "08:30-12:00-1";
        }
        String[] ranges = serviceTime.split(",");
        String[] rangeArr = ranges[0].split("-");
        int limit = 1;
        if (rangeArr.length > 2) {
            limit = Integer.parseInt(rangeArr[2]);
        }

        List<String> bookList = mtBookItemMapper.getBookList(param.getBookId(), param.getDate(), param.getTime());
        int used = bookList == null ? 0 : bookList.size();
        int remain = Math.max(limit - used, 0);

        Map<String, Object> data = new HashMap<>();
        data.put("time", param.getTime());
        data.put("remain", remain);
        data.put("limit", limit);
        result.add(data);
        return result;
    }

    /**
     * 获取预约项目列表
     *
     * @param  merchantId 商户ID
     * @param  storeId 店铺ID
     * @return
     * */
    public List<MtBook> getBookList(Integer merchantId, Integer storeId) {
        LambdaQueryWrapper<MtBook> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.eq(MtBook::getStatus, StatusEnum.ENABLED.getKey());
        if (merchantId != null && merchantId > 0) {
            lambdaQueryWrapper.eq(MtBook::getMerchantId, merchantId);
        }
        if (storeId != null && storeId > 0) {
            lambdaQueryWrapper.eq(MtBook::getStoreId, storeId);
        }

        lambdaQueryWrapper.orderByAsc(MtBook::getSort);
        List<MtBook> dataList = mtBookMapper.selectList(lambdaQueryWrapper);
        String baseImage = settingService.getUploadBasePath();

        if (dataList.size() > 0) {
            for (MtBook book : dataList) {
                book.setLogo(baseImage + book.getLogo());
            }
        }

        return dataList;
    }

    @Override
    public BigDecimal calculateBookingPayAmount(MtBookItem bookItem) throws BusinessCheckException {
        if (bookItem == null || bookItem.getBookId() == null) {
            throw new BusinessCheckException("预约信息有误");
        }
        BookDto bookInfo;
        try {
            bookInfo = getBookById(bookItem.getBookId(), false);
        } catch (Exception e) {
            throw new BusinessCheckException("预约项目不存在");
        }
        if (bookInfo == null || StringUtil.isEmpty(bookInfo.getPriceRules())) {
            throw new BusinessCheckException("未配置预约费用");
        }

        List<BookingPriceRule> rules = new ArrayList<>();
        try {
            JSONArray array = JSONArray.parseArray(bookInfo.getPriceRules());
            if (array != null) {
                for (int i = 0; i < array.size(); i++) {
                    JSONObject obj = array.getJSONObject(i);
                    if (obj == null) {
                        continue;
                    }
                    BigDecimal hours = obj.getBigDecimal("hours");
                    BigDecimal price = obj.getBigDecimal("price");
                    if (hours == null || price == null) {
                        continue;
                    }
                    if (hours.compareTo(BigDecimal.ZERO) <= 0) {
                        continue;
                    }
                    rules.add(new BookingPriceRule(hours, price));
                }
            }
        } catch (Exception e) {
            throw new BusinessCheckException("预约费用配置有误");
        }
        if (rules.isEmpty()) {
            throw new BusinessCheckException("未配置预约费用");
        }
        rules.sort(Comparator.comparing(r -> r.hours));

        BigDecimal durationHours = null;
        if (bookItem.getCheckinDurationHours() != null) {
            durationHours = bookItem.getCheckinDurationHours();
        } else if (bookItem.getCheckinStartTime() != null && bookItem.getCheckinEndTime() != null) {
            long diffMs = bookItem.getCheckinEndTime().getTime() - bookItem.getCheckinStartTime().getTime();
            double h = Math.max(diffMs / 3600000.0, 0);
            durationHours = BigDecimal.valueOf(h).setScale(1, RoundingMode.HALF_UP);
        }
        if (durationHours == null || durationHours.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessCheckException("未获取到预约时长，无法结算");
        }

        for (BookingPriceRule rule : rules) {
            if (rule.hours.compareTo(durationHours) == 0) {
                return rule.price.setScale(2, RoundingMode.HALF_UP);
            }
        }

        BookingPriceRule minRule = rules.get(0);
        BookingPriceRule maxRule = rules.get(rules.size() - 1);
        if (durationHours.compareTo(maxRule.hours) >= 0) {
            return maxRule.price.setScale(2, RoundingMode.HALF_UP);
        }
        if (durationHours.compareTo(minRule.hours) <= 0) {
            return minRule.price.setScale(2, RoundingMode.HALF_UP);
        }

        BookingPriceRule lower = null;
        BookingPriceRule upper = null;
        for (BookingPriceRule rule : rules) {
            if (rule.hours.compareTo(durationHours) < 0) {
                lower = rule;
                continue;
            }
            if (rule.hours.compareTo(durationHours) > 0) {
                upper = rule;
                break;
            }
        }
        if (lower == null || upper == null) {
            throw new BusinessCheckException("未配置预约费用");
        }

        BigDecimal span = upper.hours.subtract(lower.hours);
        if (span.compareTo(BigDecimal.ZERO) <= 0) {
            return lower.price.setScale(2, RoundingMode.HALF_UP);
        }
        BigDecimal ratio = durationHours.subtract(lower.hours).divide(span, 8, RoundingMode.HALF_UP);
        BigDecimal price = lower.price.add(upper.price.subtract(lower.price).multiply(ratio));
        return price.setScale(2, RoundingMode.HALF_UP);
    }
}
