package com.yutou.bilibili.services;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.util.DateUtils;
import com.yutou.biliapi.api.LiveApi;
import com.yutou.biliapi.bean.live.LiveRoomConfig;
import com.yutou.biliapi.bean.live.LiveRoomInfo;
import com.yutou.biliapi.bean.live.LiveRoomPlayInfo;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
import com.yutou.biliapi.bean.login.LoginCookieDatabaseBean;
import com.yutou.biliapi.databases.BiliBiliLoginDatabase;
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
import com.yutou.biliapi.databases.BiliLiveDatabase;
import com.yutou.biliapi.enums.LiveProtocol;
import com.yutou.biliapi.enums.LiveVideoCodec;
import com.yutou.biliapi.enums.LiveVideoDefinition;
import com.yutou.biliapi.enums.LiveVideoFormat;
import com.yutou.biliapi.net.BiliLiveNetApiManager;
import com.yutou.biliapi.net.WebSignManager;
import com.yutou.bilibili.Tools.DateFormatUtils;
import com.yutou.bilibili.Tools.FileServerUtils;
import com.yutou.bilibili.Tools.LiveInfoNfoTools;
import com.yutou.bilibili.Tools.ProcessUtils;
import com.yutou.bilibili.datas.VideoFilePath;
import com.yutou.bilibili.interfaces.DownloadInterface;
import com.yutou.common.okhttp.HttpCallback;
import com.yutou.common.okhttp.HttpDownloadUtils;
import com.yutou.common.record.AbsVideoRecord;
import com.yutou.common.utils.ConfigTools;
import com.yutou.common.utils.FFmpegUtils;
import com.yutou.common.utils.Log;
import okhttp3.Headers;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.alibaba.fastjson2.util.DateUtils.DateTimeFormatPattern.DATE_FORMAT_10_DASH;

@Service
public class LiveVideoDownloadService {
    private final ThreadPoolExecutor executor;
    private final List<String> userStopList = new ArrayList<>();//手动停止列表
    private final AbsVideoRecord videoRecord;

    public LiveVideoDownloadService() {
        Log.i("初始化下载服务");
        videoRecord = new FFmpegUtils();
        executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
    }

    public boolean checkDownload(String roomId) {
        return videoRecord.check(roomId);
    }

    public void clearUserStopList() {
        userStopList.clear();
    }

    public void start(LiveConfigDatabaseBean bean, boolean isUser) {
        if (!isUser && userStopList.contains(bean.getRoomId().toString())) {
            return;
        }
        if (isUser) {
            userStopList.remove(bean.getRoomId().toString());
        }
        if (videoRecord.check(bean.getRoomId().toString())) {
            return;
        }
        BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid()).getRoomInfo(bean.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() {
            @Override
            public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
                if (response.getLiveStatus() == 1) {
                    VideoTask task = new VideoTask(bean, response);
                    executor.execute(task);
                } else {
                    Log.i(bean.getRoomId(), "没有开播");
                }
            }

            @Override
            public void onFailure(Throwable throwable) {
                Log.e(throwable, "移除下载");
            }
        });


    }

    public void stop(String roomId, boolean isUser) {
        if (isUser) {
            userStopList.add(roomId);
        }
        videoRecord.kill(roomId);
    }


    public JSONArray getDownloadTasks() {
        JSONArray array = new JSONArray();
        array.addAll(videoRecord.getRoomIds());
        return array;
    }

    public void stopAll() {
        videoRecord.killAll();
    }

    private class VideoTask implements Runnable {
        LiveConfigDatabaseBean bean;
        boolean isDownload = true;
        LiveApi api;
        String savePath;
        File rootPath;
        LiveConfigDatabaseBean config;
        BiliLiveDatabase database;
        LiveVideoDatabaseBean videoDatabaseBean = null;
        LiveRoomInfo roomInfo;

        public VideoTask(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
            this.bean = bean;
            this.roomInfo = roomInfo;
            api = BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid());
        }


        @Override
        public void run() {
            if (roomInfo.getLiveStatus() == 1) {
                String time = DateUtils.format(new Date().getTime(), DATE_FORMAT_10_DASH);
                rootPath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + "[" +
                        DateUtils.format(new Date(),
                                "HH-mm-ss") + "]" + roomInfo.getTitle());
                savePath = rootPath.getAbsolutePath() + File.separator + roomInfo.getTitle() + ".m3u8";
                if (!rootPath.exists()) {
                    rootPath.mkdirs();
                }
                record(bean, roomInfo);
            } else {
                stop();
            }
        }

        private void stop() {
            videoRecord.kill(bean.getRoomId());
            api.getRoomInfo(config.getRoomId()).enqueue(new HttpCallback<LiveRoomInfo>() {
                @Override
                public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
                    if (response.getLiveStatus() == 1) {
                        LiveVideoDownloadService.this.start(bean, false);
                    } else {
                        LiveVideoDownloadService.this.stop(bean.getRoomId(), false);
                    }
                }

                @Override
                public void onFailure(Throwable throwable) {

                }
            });

        }

        private void record(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
            this.config = bean;
            isDownload = true;
            LiveRoomConfig config = new LiveRoomConfig();
            config.setLoginUid(bean.getRecordUid());
            config.setRoomId(bean.getRoomId());
            config.setAnchorName(bean.getAnchorName());
            config.setLogin(StringUtils.hasText(bean.getRecordUid()));
            config.setRoomInfo(roomInfo);
            config.setRootPath(bean.getRecordPath());
            database = new BiliLiveDatabase(config);
            try {
                var keyframe = roomInfo.getKeyframe();
                if (!StringUtils.hasText(keyframe)) {
                    keyframe = roomInfo.getUserCover();
                }
                HttpDownloadUtils.download(new HttpDownloadUtils.Builder().setUrl(keyframe)
                        .setPath(rootPath.getAbsolutePath())
                        .setFileName("poster.jpg"));
            } catch (Exception e) {
                e.printStackTrace();
            }
            saveLiveInfo(roomInfo);
            api.getLiveRoomPlayInfo(
                    bean.getRoomId().toString(),
                    LiveProtocol.getAll(),
                    LiveVideoFormat.getAll(),
                    LiveVideoCodec.getAll(),
                    LiveVideoDefinition.ORIGINAL.getValue()).enqueue(new HttpCallback<LiveRoomPlayInfo>() {
                @Override
                public void onResponse(Headers headers, int code, String status, LiveRoomPlayInfo response, String rawResponse) {
                    Random random = new Random();
                    List<LiveRoomPlayInfo.Stream> streams = response.getPlayurlInfo().getPlayurl().getStream();
                    List<LiveRoomPlayInfo.Stream> streamList = streams.stream()
                            .filter(it -> "http_stream".equals(it.getProtocolName()))
                            .toList();

                    LiveRoomPlayInfo.Stream stream = streamList.get(random.nextInt(streamList.size()));
                    if (stream == null) {
                        return;
                    }
                    List<LiveRoomPlayInfo.Format> formats = stream.getFormat().stream().filter(it -> "flv".equals(it.getFormatName())).toList();
                    LiveRoomPlayInfo.Format format = formats.get(random.nextInt(formats.size()));

                    List<LiveRoomPlayInfo.Codec> codecs = format.getCodec().stream().filter(item -> "avc".equals(item.getCodecName())).toList();

                    LiveRoomPlayInfo.Codec codec = codecs.get(random.nextInt(codecs.size()));

                    int urlIndex = random.nextInt(codec.getUrlInfo().size());
                    LiveRoomPlayInfo.UrlInfo urlInfo = codec.getUrlInfo().get(urlIndex);


                    String url = urlInfo.getHost() + codec.getBaseUrl() + urlInfo.getExtra();
                    Log.i("下载直播", rawResponse, codec.toString(), urlInfo.toString(), "URL:" + url);

                    if (bean.getRecordLiveModel() == 1) {
                        javaRecord(url, response);
                    } else {
                        ffmpeg(url, response);
                    }
                }

                @Override
                public void onFailure(Throwable throwable) {
                    Log.e(throwable);
                }
            });
        }

        private void javaRecord(String url, LiveRoomPlayInfo playInfo) {

            HttpDownloadUtils.download(new HttpDownloadUtils.Builder()
                    .setUrl(url)
                    .setPath(savePath)
                    .setDownloadInterface(new DownloadInterface() {
                        @Override
                        public void onDownloadStart() {
                            super.onDownloadStart();
                            VideoTask.this.onStart();
                        }

                        @Override
                        public boolean onDownloading(double soFarBytes, double totalBytes) {
                            return isDownload;
                        }

                        @Override
                        public void onDownload(File file) {
                            super.onDownload(file);
                            stop();
                        }

                        @Override
                        public void onError(Exception e) {
                            super.onError(e);
                            stop();
                        }
                    }));
        }

        private void ffmpeg(String url, LiveRoomPlayInfo playInfo) {
            String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
            String cookie = "";
            LoginCookieDatabaseBean ck = BiliBiliLoginDatabase.getInstance().getCookie(config.getRecordUid());
            if (ck != null) {
                cookie = ck.toCookieString();
            }

            FFmpegUtils.Builder builder = new FFmpegUtils.Builder()
                    .withParam("-user_agent", ConfigTools.getUserAgent())
                    .withParam("-headers", "Referer: https://live.bilibili.com/" + playInfo.getRoomId())
//                    .withNotSymbolParam("-reconnect", "1")
//                    .withNotSymbolParam("-reconnect_at_eof", "1")
//                    .withNotSymbolParam("-reconnect_streamed", "1")
//                    .withNotSymbolParam("-reconnect_delay_max", "2")
//                    .withNotSymbolParam("-loglevel", "error")
//                    .withNotSymbolParam("-progress", "-")
//                    .withNotSymbolParam("-fflags", "+genpts")
                    .withNotSymbolParam("-threads", "8")
//                    .withNotSymbolParam("-bufsize", "10M")
                    .withNotSymbolParam("-f", "segment")
                    .withNotSymbolParam("-segment_time", "60")
                    .withNotSymbolParam("-segment_format", "mpegts")
                    .withNotSymbolParam("-map", "0")
                    .withParam("-segment_list", savePath)
                    .withNotSymbolParam("-c", "copy")
                    .withNotSymbolParam("-bsf:a", "aac_adtstoasc")
//                    .withNotSymbolParam("-loglevel", "debug")
                    .withNotSymbolParam("-y", "")
//                    .withNotSymbolParam("-progress",new File("cache",config.getRoomId()+".txt").getAbsolutePath());  //输出进度日志,暂时没啥用
                    ;
            if (ck != null) {
                //     builder = builder.withParam("-cookies", cookie);
            }
            FFmpegUtils command = builder.build(config.getRoomId(), ffmpegPath, url, savePath.replace(".m3u8","-%04d.ts"));
            Log.i(command.getCommandDecode());
            try {
                command.start(new DownloadInterface() {
                    TimerTask task = null;

                    @Override
                    public void onDownloadStart() {
                        super.onDownloadStart();
                        task = new TimerTask() {
                            @Override
                            public void run() {
                                VideoTask.this.onStart();
                                Log.i("启动录制:" + playInfo.getRoomId());
                                task = null;
                                cancel();
                            }
                        };
                        new Timer().schedule(task, 5 * 1000);
                    }

                    @Override
                    public boolean onDownloading(double soFarBytes, double totalBytes) {
                        return super.onDownloading(soFarBytes, totalBytes);
                    }

                    @Override
                    public void onDownload(File file) {
                        super.onDownload(file);
                        if (task != null) {
                            task.cancel();
                            task = null;
                        }
                        if (videoDatabaseBean != null) {
                            videoDatabaseBean.setStopTime(new Date());
                            database.addLiveInfo(videoDatabaseBean);
                        }
                    }
                });

            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }

        private void onStart() {
            videoDatabaseBean = new LiveVideoDatabaseBean();
            videoDatabaseBean.setPath(savePath);
            videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo));
            videoDatabaseBean.setStartTime(new Date());
            database.addLiveInfo(videoDatabaseBean);
            // LiveInfoNfoTools.saveLiveInfoNfo(roomInfo, rootPath.getAbsolutePath(), new File(savePath).getName().replace(".flv", ".nfo"));

        }
    }

    private void saveLiveInfo(LiveRoomInfo roomInfo) {

    }

    public VideoFilePath getVideoPath(String roomId) {
        BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
        LiveConfigDatabaseBean bean = configDatabase.getConfig(roomId);
        configDatabase.close();
        return getVideoFilePath(bean);
    }

    private VideoFilePath getVideoFilePath(LiveConfigDatabaseBean configBean) {
        String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName();
        File recordDir = new File(recordPath);
        BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId()), recordDir.getAbsolutePath());
        VideoFilePath path = createVideoRootFilePath(configBean, recordDir);
        if (recordDir.exists()) {
            List<LiveVideoDatabaseBean> infos = database.getLiveInfos();
            path.setChildren(getVideoInfo(infos));
        }
        database.close();
        return path;
    }

    private VideoFilePath createVideoRootFilePath(LiveConfigDatabaseBean config, File db) {
        VideoFilePath path = new VideoFilePath();
        path.setRoomId(config.getRoomId());
        path.setCover(config.getAnchorFace());
        path.setName(config.getAnchorName());
        path.setUid(config.getAnchorUid());
        path.setParent(true);
        path.setPath(FileServerUtils.toUrl(db.getParent()));
        return path;
    }

    private Map<String, List<VideoFilePath>> getVideoInfo(List<LiveVideoDatabaseBean> videoList) {
        Map<String, List<VideoFilePath>> map = new HashMap<>();
        for (LiveVideoDatabaseBean bean : videoList) {
            String date = DateFormatUtils.getInstance().format(bean.getSql_time(), "yyyy-MM-dd");
            if (!map.containsKey(date)) {
                map.put(date, new ArrayList<>());
            }
            VideoFilePath path = new VideoFilePath();
            LiveRoomInfo roomInfo = JSONObject.parseObject(bean.getRoomInfoJson(), LiveRoomInfo.class);
            path.setRoomId(roomInfo.getRoomId());
            path.setName(roomInfo.getTitle());
            path.setUid(roomInfo.getUid());
            path.setPath(String.valueOf(bean.getSql_time().getTime()));
            path.setCover(roomInfo.getKeyframe());
            path.setParent(false);
            path.setChildren(null);
            map.get(date).add(path);
        }
        return map;
    }

    public String getVideoPlay(String roomId, String videoId) {
        String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
        BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
        LiveConfigDatabaseBean config = configDatabase.getConfig(roomId);
        String recordPath = config.getRecordPath() + File.separator + config.getAnchorName();
        configDatabase.close();
        LiveVideoDatabaseBean videoInfo = null;
        BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId), new File(recordPath).getAbsolutePath());
        for (LiveVideoDatabaseBean info : liveDatabase.getLiveInfos()) {
            if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) {
                videoInfo = info;
                break;
            }
        }
        liveDatabase.close();
        if (videoInfo != null) {
            File videoFile = new File(videoInfo.getPath().replace("-%04d.ts", ".m3u8"));
            if (!videoFile.exists()) {
                videoFile = new File(videoInfo.getPath());
            } else {
                return videoInfo.getPath().replace(new File("live").getAbsolutePath(), "").replace(File.separator, "/").replace("-%04d.ts", ".m3u8");
            }
            FFmpegUtils ffmpeg = FFmpegUtils.segment(videoId, ffmpegPath, videoFile, ConfigTools.load(ConfigTools.CONFIG, "outVideoPath", String.class));
            System.out.println(ffmpeg.getCommandDecode());
            ffmpeg.start(new DownloadInterface() {
                @Override
                public void onDownload(File file) {
                    super.onDownload(file);

                }
            });
            return ffmpeg.getOutputFilePath();
        }
        return null;
    }

    public static void main(String[] args) {
        LiveVideoDownloadService service = new LiveVideoDownloadService();
        String play = service.getVideoPlay("17961", "1730363029293");
        System.out.println(play);
    }
}
