package ctbrec.sites.amateurtv;

import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import ctbrec.recorder.download.AbstractDownload;
import ctbrec.recorder.download.RecordingProcess;
import ctbrec.recorder.download.StreamSource;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;

import static ctbrec.io.HttpConstants.*;

public class AmateurTvDownload extends AbstractDownload {

    private static final Logger LOG = LoggerFactory.getLogger(AmateurTvDownload.class);
    private static final int MAX_SECONDS_WITHOUT_TRANSFER = 20;

    private final HttpClient httpClient;
    private FileOutputStream fout;
    private Instant timeOfLastTransfer = Instant.MAX;

    private volatile boolean running;
    private volatile boolean started;

    private File targetFile;

    public AmateurTvDownload(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    @Override
    public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
        this.config = config;
        this.model = model;
        this.startTime = startTime;
        this.downloadExecutor = executorService;
        splittingStrategy = initSplittingStrategy(config.getSettings());
        targetFile = config.getFileForRecording(model, "mp4", startTime);
        timeOfLastTransfer = Instant.now();
    }

    @Override
    public void stop() {
        running = false;
    }

    @Override
    public void finalizeDownload() {
        if (fout != null) {
            try {
                LOG.debug("Closing recording file {}", targetFile);
                fout.close();
            } catch (IOException e) {
                LOG.error("Error while closing recording file {}", targetFile, e);
            }
        }
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    @Override
    public void postProcess(Recording recording) {
        // nothing to do
    }

    @Override
    public File getTarget() {
        return targetFile;
    }

    @Override
    public String getPath(Model model) {
        String absolutePath = targetFile.getAbsolutePath();
        String recordingsDir = Config.getInstance().getSettings().recordingsDir;
        String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
        return relativePath;
    }

    @Override
    public boolean isSingleFile() {
        return true;
    }

    @Override
    public long getSizeInByte() {
        return getTarget().length();
    }

    @Override
    public RecordingProcess call() throws Exception {
        if (!started) {
            started = true;
            startDownload();
        }

        if (splittingStrategy.splitNecessary(this)) {
            stop();
            rescheduleTime = Instant.now();
        } else {
            rescheduleTime = Instant.now().plusSeconds(5);
        }
        if (!model.isOnline(true)) {
            stop();
        }
        if (Duration.between(timeOfLastTransfer, Instant.now()).getSeconds() > MAX_SECONDS_WITHOUT_TRANSFER) {
            LOG.info("No video data received for {} seconds. Stopping recording for model {}", MAX_SECONDS_WITHOUT_TRANSFER, model);
            stop();
        }
        return this;
    }

    private void startDownload() {
        downloadExecutor.submit(() -> {
            running = true;
            try {
                StreamSource src = model.getStreamSources().get(0);
                LOG.debug("Loading video from {}", src.getMediaPlaylistUrl());
                Request request = new Request.Builder()
                        .url(src.getMediaPlaylistUrl())
                        .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
                        .header(ACCEPT, "*/*")
                        .header(ACCEPT_LANGUAGE, "en")
                        .header(ORIGIN, AmateurTv.BASE_URL)
                        .build();
                try (Response resp = httpClient.execute(request)) {
                    if (resp.isSuccessful()) {
                        LOG.debug("Recording video stream to {}", targetFile);
                        Files.createDirectories(targetFile.getParentFile().toPath());
                        fout = new FileOutputStream(targetFile);

                        InputStream in = Objects.requireNonNull(resp.body()).byteStream();
                        byte[] b = new byte[1024];
                        int len;
                        while (running && !Thread.currentThread().isInterrupted() && (len = in.read(b)) >= 0) {
                            fout.write(b, 0, len);
                            timeOfLastTransfer = Instant.now();
                            getDownloadedBytes().addAndGet(len);
                            BandwidthMeter.add(len);
                        }
                    } else {
                        throw new HttpException(resp.code(), resp.message());
                    }
                }
            } catch (Exception e) {
                LOG.error("Error while downloading MP4", e);
            }
            running = false;
        });
    }

}
