package link.at17.mid.tushare.data.service;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.Temporal;
import java.util.List;

import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;

import link.at17.mid.tushare.annotation.UpdateMethod;
import link.at17.mid.tushare.dao.StockCalendarDao;
import link.at17.mid.tushare.data.crawler.tushare.TushareClient;
import link.at17.mid.tushare.data.crawler.tushare.TushareRequestBody;
import link.at17.mid.tushare.data.models.StockCalendar;
import link.at17.mid.tushare.enums.StockMarket;
import link.at17.mid.tushare.service.BaseServiceImpl;
import link.at17.mid.tushare.system.util.LocalDateTimeUtils;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class StockCalendarService extends BaseServiceImpl<StockCalendarDao, StockCalendar> {

    private static final StockMarket[] UPDATE_SUPPORTED_STOCK_MARKETS = new StockMarket[] {StockMarket.SZ, StockMarket.SH};

    /**
     * <a href="https://tushare.pro/document/2?doc_id=26"><b>交易日历</b></a><br>
     * <b>更新交易日历</b>
     * <p>
     * 描述：获取各大交易所交易日历数据,默认提取的是上交所<br>
     * 积分：需2000积分
     * </p>
     * @param stockMarkets 市场类型，若未提供则默认更新上交所和深交所
     */
    @UpdateMethod(name="交易日历", order=0)
    public boolean updateData() {
        
        LocalDate lastDayOfThisYear = LocalDateTimeUtils.getLastDayOfThisYear();
        
        for (StockMarket stockMarket : UPDATE_SUPPORTED_STOCK_MARKETS) {
            
            log.debug("正在更新 {} 交易所的交易日历", stockMarket);
            
            LocalDate latest = getLatestLocalDate(stockMarket);
            if (latest != null && latest.equals(lastDayOfThisYear)){
                log.debug("{} 交易所的日历已更新到本年度最后一日，跳过", stockMarket);
                continue;
            }
            TushareRequestBody rq = new TushareRequestBody("trade_cal")
                    .addParam("exchange", stockMarket.getExchangeCode())
                    .addDateParam("end_date", lastDayOfThisYear)
                    .addFields("exchange,cal_date,is_open,pretrade_date");
            if (latest != null) {
                rq.addDateParam("start_date", latest);
            }
            try {
                List<JSONObject> list = TushareClient.queryList(rq);
                baseMapper.insertOrUpdateList(list); 
            } catch (JSONException e) {
                log.error("JSONException", e);
                return false;
            } catch (IOException e) {
                log.error("IOException", e);
                return false;
            }
        }
        return true;
    }
    
    /**
     * 插入从 Tushare 获取的数据
     * @param list
     * @return 插入是否成功
     */
    public boolean insertOrUpdateList(List<JSONObject> list) {
        return SqlHelper.retBool(baseMapper.insertOrUpdateList(list));
    }

    /**
     * 获取指定证交所两个日期（含）之间的交易日个数<br>
     * <p>
     * 存储在数据库内的类型为无时分秒的 timestamp 类型，可视作 {@code java.time.LocalDate} <br>
     * 若传入的是 java.time.LocalDateTime 类型，会忽略时分秒
     * <p>例：
     * <ul><li>
     * <code>startDate&nbsp;: LocalDateTime 2021-08-16 01:20:30</code><br>
     * <code>endDate&nbsp;&nbsp;&nbsp;: LocalDateTime 2021-08-20 19:20:30</code><br>
     * 返回 5
     * <li><code>countOpenDaysBetween(StockMarket.<b><i>SH</i></b>, startDate, endDate)</code> 其中<br>
     * <code>startDate&nbsp;: LocalDateTime 2021-08-16 01:20:30</code><br>
     * <code>endDate&nbsp;&nbsp;&nbsp;: LocalDateTime 2021-08-16 12:34:56</code><br>
     * 返回 1
     * <li><code>countOpenDaysBetween(StockMarket.<b><i>SH</i></b>, startDate, endDate)</code> 其中<br>
     * <code>startDate&nbsp;: LocalDateTime 2021-08-15 01:20:30</code><br>
     * <code>endDate&nbsp;&nbsp;&nbsp;: LocalDateTime 2021-08-16 12:34:56</code><br>
     * 返回 1
     * </ul>
     * </p>
     * @param exchange 股市类型枚举，若提供的枚举不为 SZ 或 SH，则采用 SZ + SH 的所有交易日并去重计算
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return
     
    */
    public long countOpenDaysBetween(StockMarket exchange, Temporal startDate, Temporal endDate) {
        return baseMapper.countOpenDaysBetween(exchange, startDate, endDate);
    }
    
    /**
     * 获取指定证交所两个日期（含）之间的所有交易日<br>
     * 存储在数据库内的类型为无时分秒的 timestamp 类型，可视作 {@code java.time.LocalDate} <br>
     * 若传入的是 java.time.LocalDateTime 类型，会忽略时分秒
     * <p>例：
     * <ul><li><code>countOpenDaysBetween(StockMarket.<b><i>SH</i></b>, startDate, endDate)</code> 其中<br>
     * <code>startDate&nbsp;: LocalDateTime 2021-08-16 01:20:30</code><br>
     * <code>endDate&nbsp;&nbsp;: LocalDateTime 2021-08-20 19:20:30</code><br>
     * </p>
     * <p>
     * 返回：
     * <ul>
     * <li>2021-08-16
     * <li>2021-08-17
     * <li>2021-08-18
     * <li>2021-08-19
     * <li>2021-08-20
     * </ul>
     * </p>
     * @param exchange 股市类型枚举，若提供的枚举不为 SZ 或 SH，则采用 SZ + SH 的所有交易日并去重获得结果
     * @param startDate 开始日期(含)
     * @param endDate 结束日期(含)
     * @return
     
    */
    public List<LocalDateTime> getAllOpenDatesBetween(StockMarket exchange, Temporal startDate, Temporal endDate){
        return baseMapper.getAllOpenDatesBetween(exchange, startDate, endDate);
    }
    
    /**
     * 获取指定证交所指定日期偏移的指定交易日
     * @param exchange
     * @param date
     * @param offset
     * @return
     */
    public LocalDateTime getOpenDateOffset(StockMarket exchange, Temporal date, long offset) {
        return baseMapper.getOpenDateOffset(exchange, date, offset);
    }
    

    /**
     * 获取指定证交所指定日期（含）以后的所有交易日
     * @param exchange
     * @param sence 留空则查询所有
     * @return
     */
    public List<LocalDateTime> getAllOpenDateSence(StockMarket exchange, @Nullable Temporal sence) {
        return baseMapper.getAllOpenDateSence(exchange, sence);
    }

    /**
     * 判断指定日是否为指定交易所的交易日
     * @param date 会截断时分秒，只取年月日
     * @param exchange 目前只支持 SSE（深交所）和 SHSE（上交所）
     * @return
     * @see #isOpen(Temporal)
     */
    public boolean isOpen(@NonNull Temporal date, @Nullable StockMarket exchange) {
        return baseMapper.isOpen(date, exchange);
    }

    /**
     * 判断指定日是否为任意交易所的交易日
     * @param date 会截断时分秒，只取年月日
     * @return
     * @see #isOpen(Temporal, StockMarket)
     */
    public boolean isOpen(@NonNull Temporal date) {
        return baseMapper.isOpen(date, null);
    }
    
    /**
     * 获取数据库内指定证交所的最早一个交易日历
     * <p>仅为日期，并未指定是否是开市日
     * @param stockMarket 指定证交所，若为 {@code null} 则取最早一条（市场不确定）
     * @return
     */
    public StockCalendar getGreatest(@Nullable StockMarket stockMarket) {
        return baseMapper.getGreatest(stockMarket);
    }

    /**
     * 获取数据库内指定证交所的最早一个交易日历
     * <p>仅为日期，并未指定是否是开市日。取最早一条（市场不确定）
     * @see #getGreatest(StockMarket)
     * @return
     */
    public StockCalendar getGreatest() {
        return getGreatest(null);
    }

    /**
     * 获取数据库内指定证交所的最早一个交易日历的日期
     * <p>仅为日期，并未指定是否是开市日。取最早一条（市场不确定）
     * @see #getGreatest
     * @return {@code LocalDate} 或 {@code null}
     */
    public LocalDate getGreatestLocalDate() {
        return getGreatestLocalDate(null);
    }
    
    /**
     * 获取数据库内指定证交所的最早一个交易日历的日期
     * <p>仅为日期，并未指定是否是开市日
     * @see #getGreatest
     * @param stockMarket 指定证交所，若为 {@code null} 则取最早一条（市场不确定）
     * @return {@code LocalDate} 或 {@code null}
     */
    public LocalDate getGreatestLocalDate(@Nullable StockMarket stockMarket) {
        StockCalendar stockCalendar = getGreatest(stockMarket);
        if (stockCalendar != null) {
            return stockCalendar.getDate().atStartOfDay().toLocalDate();
        }
        return null;
    }

    /**
     * 获取数据库内指定证交所的最早一个交易日历的日期时间
     * <p>仅为日期，并未指定是否是开市日。取最早一条（市场不确定）
     * @see #getGreatest
     * @return {@code LocalDate} 或 {@code null}
     */
    public LocalDateTime getGreatestLocalDateTime() {
        return getGreatestLocalDateTime(null);
    }
    
    /**
     * 获取数据库内指定证交所的最早一个交易日历的日期时间
     * <p>仅为日期，并未指定是否是开市日
     * @see #getGreatest
     * @param stockMarket 指定证交所，若为 {@code null} 则取最早一条（市场不确定）
     * @return {@code LocalDateTime} 或 {@code null}
     */
    public LocalDateTime getGreatestLocalDateTime(@Nullable StockMarket stockMarket) {
        StockCalendar stockCalendar = getGreatest(stockMarket);
        if (stockCalendar != null) {
            return stockCalendar.getDate().atStartOfDay();
        }
        return null;
    }

    
    /**
     * 获取数据库内指定证交所的最新一个交易日历
     * <p>仅为日期，并未指定是否是开市日
     * @param stockMarket 指定证交所，若为 {@code null} 则取最新一条（市场不确定）
     * @return
     */
    public StockCalendar getLatest(@Nullable StockMarket stockMarket) {
        return baseMapper.getLatest(stockMarket);
    }
    
    /**
     * 获取数据库内指定证交所的最近一个交易日历
     * <p>仅为日期，并未指定是否是开市日。取最近一条（市场不确定）
     * @see #getLatest(StockMarket)
     * @return
     */
    public StockCalendar getLatest() {
        return getLatest(null);
    }
    

    /**
     * 获取数据库内指定证交所的最近一个交易日历的日期
     * <p>仅为日期，并未指定是否是开市日
     * @see #getGreatest
     * @param stockMarket 指定证交所，若为 {@code null} 则取最近一条（市场不确定）
     * @return {@code LocalDate} 或 {@code null}
     */
    public LocalDate getLatestLocalDate(@Nullable StockMarket stockMarket) {
        StockCalendar stockCalendar = getLatest(stockMarket);
        if (stockCalendar != null) {
            return stockCalendar.getDate().atStartOfDay().toLocalDate();
        }
        return null;
    }

    /**
     * 获取数据库内指定证交所的最近一个交易日历的日期
     * <p>仅为日期，并未指定是否是开市日。取最近一条（市场不确定）
     * @see #getGreatest
     * @return {@code LocalDate} 或 {@code null}
     */
    public LocalDate getLatestLocalDate() {
        return getLatestLocalDate(null);
    }
    
    /**
     * 获取数据库内指定证交所的最近一个交易日历的日期时间
     * <p>仅为日期时间，并未指定是否是开市日
     * @see #getGreatest
     * @param stockMarket 指定证交所，若为 {@code null} 则取最近一条（市场不确定）
     * @return {@code LocalDateTime} 或 {@code null}
     */
    public LocalDateTime getLatestLocalDateTime(@Nullable StockMarket stockMarket) {
        StockCalendar stockCalendar = getLatest(stockMarket);
        if (stockCalendar != null) {
            return stockCalendar.getDate().atStartOfDay();
        }
        return null;
    }
    

    /**
     * 获取数据库内指定证交所的最近一个交易日历的日期时间
     * <p>仅为日期时间，并未指定是否是开市日。取最近一条（市场不确定）
     * @see #getGreatest
     * @return {@code LocalDateTime} 或 {@code null}
     */
    public LocalDateTime getLatestLocalDateTime() {
        return getLatestLocalDateTime(null);
    }

    /**
     * 查询今日是否开市，任意市场开市都返回 true
     * @see #todayIsOpen(StockMarket)
     * @return
     */
    public boolean todayIsOpen() {
        return baseMapper.isOpen(LocalDateTime.now(), null);
    }
    
    
    /**
     * 查询今日是否是开市日
     * @param stockMarket 指定市场，为 null 则任意市场开市都返回 true
     * @return
     */
    public boolean todayIsOpen(StockMarket stockMarket) {
        return baseMapper.isOpen(LocalDateTime.now(), stockMarket);
    }

}
