package ctbrec.recorder.server;

import static javax.servlet.http.HttpServletResponse.*;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;

import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.io.InstantJsonAdapter;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;

public class RecorderServlet extends AbstractCtbrecServlet {

    private static final transient Logger LOG = LoggerFactory.getLogger(RecorderServlet.class);

    private Recorder recorder;

    private List<Site> sites;

    public RecorderServlet(Recorder recorder, List<Site> sites) {
        this.recorder = recorder;
        this.sites = sites;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(SC_OK);
        resp.setContentType("application/json");

        String json = null;
        try {
            json = body(req);
            boolean isRequestAuthenticated = checkAuthentication(req, json);
            if (!isRequestAuthenticated) {
                resp.setStatus(SC_UNAUTHORIZED);
                String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
                resp.getWriter().write(response);
                return;
            }

            LOG.debug("Request: {}", json);
            Moshi moshi = new Moshi.Builder()
                    .add(Instant.class, new InstantJsonAdapter())
                    .add(Model.class, new ModelJsonAdapter(sites))
                    .build();
            JsonAdapter<Request> requestAdapter = moshi.adapter(Request.class);
            Request request = requestAdapter.fromJson(json);
            if (request.action != null) {
                switch (request.action) {
                case "start":
                    LOG.debug("Starting recording for model {} - {}", request.model.getName(), request.model.getUrl());
                    recorder.startRecording(request.model);
                    String response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
                    resp.getWriter().write(response);
                    break;
                case "startByUrl":
                    LOG.debug("Starting recording for model {}", request.model.getUrl());
                    startByUrl(request);
                    response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
                    resp.getWriter().write(response);
                    break;
                case "stop":
                    new Thread(() -> {
                        try {
                            recorder.stopRecording(request.model);
                        } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
                            LOG.error("Couldn't stop recording for model {}", request.model, e);
                        }
                    }).start();
                    response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
                    resp.getWriter().write(response);
                    break;
                case "list":
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"List of models\", \"models\": [");
                    JsonAdapter<Model> modelAdapter = new ModelJsonAdapter();
                    List<Model> models = recorder.getModels();
                    for (Iterator<Model> iterator = models.iterator(); iterator.hasNext();) {
                        Model model = iterator.next();
                        resp.getWriter().write(modelAdapter.toJson(model));
                        if(iterator.hasNext()) {
                            resp.getWriter().write(',');
                        }
                    }
                    resp.getWriter().write("]}");
                    break;
                case "listOnline":
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"List of online models\", \"models\": [");
                    modelAdapter = new ModelJsonAdapter();
                    models = recorder.getOnlineModels();
                    for (Iterator<Model> iterator = models.iterator(); iterator.hasNext();) {
                        Model model = iterator.next();
                        resp.getWriter().write(modelAdapter.toJson(model));
                        if(iterator.hasNext()) {
                            resp.getWriter().write(',');
                        }
                    }
                    resp.getWriter().write("]}");
                    break;
                case "recordings":
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
                    JsonAdapter<Recording> recAdapter = moshi.adapter(Recording.class);
                    List<Recording> recordings = recorder.getRecordings();
                    for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext();) {
                        Recording recording = iterator.next();
                        String recJSON = recAdapter.toJson(recording);
                        LOG.debug("Rec: {}", recJSON);
                        resp.getWriter().write(recJSON);
                        if (iterator.hasNext()) {
                            resp.getWriter().write(',');
                        }
                    }
                    resp.getWriter().write("]}");
                    break;
                case "delete":
                    recorder.delete(request.recording);
                    recAdapter = moshi.adapter(Recording.class);
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
                    resp.getWriter().write(recAdapter.toJson(request.recording));
                    resp.getWriter().write("]}");
                    break;
                case "pin":
                    recorder.pin(request.recording);
                    recAdapter = moshi.adapter(Recording.class);
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
                    resp.getWriter().write(recAdapter.toJson(request.recording));
                    resp.getWriter().write("]}");
                    break;
                case "unpin":
                    recorder.unpin(request.recording);
                    recAdapter = moshi.adapter(Recording.class);
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
                    resp.getWriter().write(recAdapter.toJson(request.recording));
                    resp.getWriter().write("]}");
                    break;
                case "rerunPostProcessing":
                    recorder.rerunPostProcessing(request.recording);
                    recAdapter = moshi.adapter(Recording.class);
                    resp.getWriter().write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}");
                    break;
                case "switch":
                    recorder.switchStreamSource(request.model);
                    response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
                    resp.getWriter().write(response);
                    break;
                case "suspend":
                    LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
                    new Thread(() -> {
                        try {
                            recorder.suspendRecording(request.model);
                        } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
                            LOG.error("Couldn't suspend recording for model {}", request.model, e);
                        }
                    }).start();
                    response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
                    resp.getWriter().write(response);
                    break;
                case "resume":
                    LOG.debug("Resume recording for model {} - {}", request.model.getName(), request.model.getUrl());
                    recorder.resumeRecording(request.model);
                    response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}";
                    resp.getWriter().write(response);
                    break;
                case "space":
                    response = "{\"status\": \"success\", \"spaceTotal\": "+recorder.getTotalSpaceBytes()+", \"spaceFree\": "+recorder.getFreeSpaceBytes()+"}";
                    resp.getWriter().write(response);
                    break;
                case "changePriority":
                    recorder.priorityChanged(request.model);
                    response = "{\"status\": \"success\"}";
                    resp.getWriter().write(response);
                    break;
                default:
                    resp.setStatus(SC_BAD_REQUEST);
                    response = "{\"status\": \"error\", \"msg\": \"Unknown action ["+request.action+"]\"}";
                    resp.getWriter().write(response);
                    break;
                }
            } else {
                resp.setStatus(SC_BAD_REQUEST);
                String response = "{\"status\": \"error\", \"msg\": \"action is missing\"}";
                resp.getWriter().write(response);
            }
        } catch(Throwable t) {
            resp.setStatus(SC_INTERNAL_SERVER_ERROR);
            JSONObject response = new JSONObject();
            response.put("status", "error");
            response.put("msg", t.getMessage());
            resp.getWriter().write(response.toString());
            LOG.error("Unexpected error", t);
            if (json != null) {
                LOG.debug("Request: {}", json);
            }
        }
    }

    private void startByUrl(Request request) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, IOException {
        String url = request.model.getUrl();
        for (Site site : sites) {
            Model model = site.createModelFromUrl(url);
            if (model != null) {
                recorder.startRecording(model);
            }
        }
    }

    private static class Request {
        public String action;
        public Model model;
        public Recording recording;
    }
}
