package org.f0w.k2i.core.exchange;

import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.f0w.k2i.core.exchange.processor.ResponseProcessor;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.parser.Parser;

import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;

import static org.f0w.k2i.core.util.exception.ExceptionUtils.rethrowSupplier;

/**
 * Generic interface for classes implementing send request - get response - handle response chain.
 *
 * @param <IN>  Input
 * @param <OUT> Output
 */
public interface Exchangeable<IN, OUT> {
    ExchangeObject<OUT> prepare(@NonNull IN param) throws IOException;

    /**
     * Interface for object which performs lazy execution of already prepared request
     * @param <OUT> Type of processed response
     */
    @Slf4j
    class ExchangeObject<OUT> {
        public static final Connection.Request EMPTY_REQUEST = new EmptyRequest();

        @NonNull
        @Getter
        private final Connection.Request request;

        @NonNull
        @Setter
        private Connection.Response response;

        @NonNull
        private final Supplier<Connection.Response> responseSupplier;

        @NonNull
        @Getter
        private final ResponseProcessor<OUT> responseProcessor;

        public ExchangeObject(Connection.Request request, ResponseProcessor<OUT> responseProcessor) throws IOException {
            this.request = request;
            this.responseProcessor = responseProcessor;
            this.responseSupplier = rethrowSupplier(() -> Jsoup.connect(request.url().toString())
                    .request(request)
                    .execute()
            );
        }

        public ExchangeObject(ResponseProcessor<OUT> responseProcessor) throws IOException {
            this(EMPTY_REQUEST, responseProcessor);
        }

        public Connection.Response getResponse() {
            if (response == null && !request.equals(EMPTY_REQUEST)) {
                log.debug("Sending request, to url: {}, with headers: {}", getRequest().url(), getRequest().headers());
                response = responseSupplier.get();
                // This wont work for failed requests, because of exception happened above
                log.debug("Got response, status code: {}, headers: {}", response.statusCode(), response.headers());
            }

            return response;
        }

        public OUT getProcessedResponse() throws HttpStatusException {
            return responseProcessor.process(getResponse());
        }

        private static final class EmptyRequest implements Connection.Request {
            @Override
            public int timeout() {
                return 0;
            }

            @Override
            public Connection.Request timeout(int millis) {
                return null;
            }

            @Override
            public int maxBodySize() {
                return 0;
            }

            @Override
            public Connection.Request maxBodySize(int bytes) {
                return null;
            }

            @Override
            public boolean followRedirects() {
                return false;
            }

            @Override
            public Connection.Request followRedirects(boolean followRedirects) {
                return null;
            }

            @Override
            public boolean ignoreHttpErrors() {
                return false;
            }

            @Override
            public Connection.Request ignoreHttpErrors(boolean ignoreHttpErrors) {
                return null;
            }

            @Override
            public boolean ignoreContentType() {
                return false;
            }

            @Override
            public Connection.Request ignoreContentType(boolean ignoreContentType) {
                return null;
            }

            @Override
            public boolean validateTLSCertificates() {
                return false;
            }

            @Override
            public void validateTLSCertificates(boolean value) {

            }

            @Override
            public Connection.Request data(Connection.KeyVal keyval) {
                return null;
            }

            @Override
            public Collection<Connection.KeyVal> data() {
                return null;
            }

            @Override
            public Connection.Request parser(Parser parser) {
                return null;
            }

            @Override
            public Parser parser() {
                return null;
            }

            @Override
            public Connection.Request postDataCharset(String charset) {
                return null;
            }

            @Override
            public String postDataCharset() {
                return null;
            }

            @Override
            public URL url() {
                return null;
            }

            @Override
            public Connection.Request url(URL url) {
                return null;
            }

            @Override
            public Connection.Method method() {
                return null;
            }

            @Override
            public Connection.Request method(Connection.Method method) {
                return null;
            }

            @Override
            public String header(String name) {
                return null;
            }

            @Override
            public Connection.Request header(String name, String value) {
                return null;
            }

            @Override
            public boolean hasHeader(String name) {
                return false;
            }

            @Override
            public boolean hasHeaderWithValue(String name, String value) {
                return false;
            }

            @Override
            public Connection.Request removeHeader(String name) {
                return null;
            }

            @Override
            public Map<String, String> headers() {
                return null;
            }

            @Override
            public String cookie(String name) {
                return null;
            }

            @Override
            public Connection.Request cookie(String name, String value) {
                return null;
            }

            @Override
            public boolean hasCookie(String name) {
                return false;
            }

            @Override
            public Connection.Request removeCookie(String name) {
                return null;
            }

            @Override
            public Map<String, String> cookies() {
                return null;
            }
        }
    }
}