package ctbrec.sites.chaturbate;

import ctbrec.Config;
import ctbrec.io.HtmlParser;
import ctbrec.io.HttpClient;
import okhttp3.*;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.Semaphore;

import static ctbrec.io.HttpConstants.REFERER;
import static ctbrec.io.HttpConstants.USER_AGENT;

public class ChaturbateHttpClient extends HttpClient {

    private static final Logger LOG = LoggerFactory.getLogger(ChaturbateHttpClient.class);
    private static final String PATH = "/auth/login/"; // NOSONAR
    protected String token;

    private static final Semaphore requestThrottle = new Semaphore(2, true);
    private static long lastRequest = 0;

    public ChaturbateHttpClient(Config config) {
        super("chaturbate", config);
    }

    private void extractCsrfToken(Request request) {
        try {
            Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken");
            token = csrfToken.value();
        } catch (NoSuchElementException e) {
            LOG.trace("CSRF token not found in cookies");
        }
    }

    public String getToken() throws IOException {
        if (token == null) {
            login();
        }
        return token;
    }

    @Override
    public boolean login() throws IOException {
        if (loggedIn) {
            return true;
        }

        if (checkLogin()) {
            loggedIn = true;
            LOG.debug("Logged in with cookies");
            return true;
        }

        try {
            Request login = new Request.Builder()
                    .url(Chaturbate.baseUrl + PATH)
                    .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
                    .build();
            Response response = client.newCall(login).execute();
            String content = response.body().string();
            token = HtmlParser.getTag(content, "input[name=csrfmiddlewaretoken]").attr("value");
            LOG.debug("csrf token is {}", token);

            RequestBody body = new FormBody.Builder()
                    .add("username", Config.getInstance().getSettings().chaturbateUsername)
                    .add("password", Config.getInstance().getSettings().chaturbatePassword)
                    .add("next", "")
                    .add("csrfmiddlewaretoken", token)
                    .build();
            login = new Request.Builder()
                    .url(Chaturbate.baseUrl + PATH)
                    .header(REFERER, Chaturbate.baseUrl + PATH)
                    .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
                    .post(body)
                    .build();

            response = client.newCall(login).execute();
            if (response.isSuccessful()) {
                content = response.body().string();
                if (content.contains("Login, Chaturbate login")) {
                    loggedIn = false;
                } else {
                    loggedIn = true;
                    extractCsrfToken(login);
                }
            } else {
                if (loginTries++ < 3) {
                    login();
                } else {
                    throw new IOException("Login failed: " + response.code() + " " + response.message());
                }
            }
            response.close();
        } finally {
            loginTries = 0;
        }
        return loggedIn;
    }

    public boolean checkLogin() throws IOException {
        String url = "https://chaturbate.com/p/" + Config.getInstance().getSettings().chaturbateUsername + "/";
        Request req = new Request.Builder()
                .url(url)
                .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
                .build();
        try (Response resp = execute(req)) {
            if (resp.isSuccessful()) {
                String profilePage = resp.body().string();
                try {
                    Element userIcon = HtmlParser.getTag(profilePage, "img.user_information_header_icon");
                    return !Objects.equals("Anonymous Icon", userIcon.attr("alt"));
                } catch (Exception e) {
                    LOG.debug("Token tag not found. Login failed");
                    return false;
                }
            } else {
                throw new IOException("HTTP response: " + resp.code() + " - " + resp.message());
            }
        }
    }

    @Override
    public Response execute(Request req) throws IOException {
        boolean throttled = req.url().host().contains("chaturbate.com");
        return executeThrottled(req, throttled);
    }

    private Response executeThrottled(Request req, boolean throttle) throws IOException {
        try {
            if (throttle) {
                acquireSlot();
            }
            Response resp = super.execute(req);
            extractCsrfToken(req);
            return resp;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new InterruptedIOException("Interrupted during request");
        } finally {
            if (throttle) {
                releaseSlot();
            }
        }
    }

    private static void acquireSlot() throws InterruptedException {
        long pauseBetweenRequests = Config.getInstance().getSettings().chaturbateMsBetweenRequests;
        requestThrottle.acquire();
        long now = System.currentTimeMillis();
        long millisSinceLastRequest = now - lastRequest;
        if (millisSinceLastRequest < pauseBetweenRequests) {
            Thread.sleep(pauseBetweenRequests - millisSinceLastRequest);
        }
    }

    private static void releaseSlot() {
        lastRequest = System.currentTimeMillis();
        requestThrottle.release();
    }
}
