package org.jboss.resteasy.test.resource;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response;

import org.jboss.logging.Logger;
import org.jboss.resteasy.utils.CookieUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @tpSubChapter Resource tests
 * @tpChapter Unit tests
 * @tpTestCaseDetails Set of tests for Cookie and NewCookie objects
 * @tpSince RESTEasy 3.0.16
 */
public class CookieObjectTest {

    private static Logger logger = Logger.getLogger(CookieObjectTest.class);

    /**
     * @tpTestDetails Create a version 0 Cookie instance by Parsing a String
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testParse1() throws Exception {
        boolean pass = true;

        String cookie_toParse = "NAME_1=Value_1;";
        String name = "name_1";
        String value = "value_1";
        String path = "";
        String domain = "";
        int version = 0;

        Cookie ck6 = CookieUtil.valueOf(Cookie.class, cookie_toParse);
        pass = testCookie(ck6, name, value, path, domain, version);
    }

    /**
     * @tpTestDetails Create a version 1 Cookie instance by Parsing a String
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testParse2() throws Exception {
        boolean pass = true;
        String cookie_toParse = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"";

        String name = "customer";
        String value = "wile_e_coyote";
        String path = "/acme";
        String domain = "";
        int version = 1;

        Cookie ck7 = CookieUtil.valueOf(Cookie.class, cookie_toParse);

        pass = testCookie(ck7, name, value, path, domain, version);
    }

    /**
     * @tpTestDetails Test if correct exception is thrown when calling Cookie.valueOf(null)
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testParse3() throws Exception {

        try {
            Cookie ck8 = CookieUtil.valueOf(Cookie.class, null);
            throw new Exception("Expectecd IllegalArgumentException not thrown.  Test Failed");
        } catch (java.lang.IllegalArgumentException ilex) {
        }
    }

    /**
     * @tpTestDetails Create a version 1 NewCookie instance by Parsing a String
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testNewCookie1() throws Exception {
        boolean pass = true;

        String NewCookie_toParse = "NAME_1=Value_1;";
        String name = "name_1";
        String value = "value_1";
        String path = "";
        String domain = "";
        int version = 1;

        NewCookie nck26 = CookieUtil.valueOf(NewCookie.class, NewCookie_toParse);

        pass = verifyNewCookie(nck26, name, value, path, domain, version,
                "", -1, null, false, false);

        // check round-tripping
        Assertions.assertEquals(nck26, CookieUtil.valueOf(NewCookie.class,
                CookieUtil.toString(NewCookie.class, nck26)));
    }

    /**
     * @tpTestDetails Create a version 1 NewCookie instance by Parsing a String
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testNewCookie2() throws Exception {
        boolean pass = true;
        String NewCookie_toParse = "Customer=WILE_E_COYOTE; Path=/acme; Version=1";

        String name = "customer";
        String value = "wile_e_coyote";
        String path = "/acme";
        String domain = "";
        int version = 1;

        NewCookie nck27 = CookieUtil.valueOf(NewCookie.class, NewCookie_toParse);

        pass = verifyNewCookie(nck27, name, value, path, domain, version,
                "", -1, null, false, false);

        // check round-tripping
        Assertions.assertEquals(nck27, CookieUtil.valueOf(NewCookie.class,
                CookieUtil.toString(NewCookie.class, nck27)));
    }

    /**
     * @tpTestDetails Create a NewCookie instance by Parsing a null String. Verify that IllegalArgumentException is thrown.
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testNewCookie3() throws Exception {
        try {
            NewCookie nck27 = CookieUtil.valueOf(NewCookie.class, null);
            throw new Exception("Expected IllegalArgumentException not thrown. Test Failed.");
        } catch (IllegalArgumentException ilex) {

        }
    }

    /**
     * @tpTestDetails Create a version 1 NewCookie instance by Parsing a String
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testNewCookie4() throws Exception {
        boolean pass = true;
        String NewCookie_toParse = "Customer=WILE_E_COYOTE; Path=/acme; Domain=acme.com; Max-Age=150000000; " +
                "Expires=Thu, 03-May-2018 10:36:34 GMT; Secure; HttpOnly";

        String name = "customer";
        String value = "wile_e_coyote";
        String path = "/acme";
        String domain = "acme.com";
        int version = 1;
        String comment = "";
        int maxAge = 150000000;
        GregorianCalendar cal = new GregorianCalendar(2018, Calendar.MAY, 3, 10, 36, 34);
        cal.setTimeZone(TimeZone.getTimeZone("UTC"));
        Date expiry = cal.getTime();
        boolean secure = true;
        boolean httpOnly = true;

        NewCookie nck28 = CookieUtil.valueOf(NewCookie.class, NewCookie_toParse);

        pass = verifyNewCookie(nck28, name, value, path, domain, version,
                comment, maxAge, expiry, secure, httpOnly);

        // check round-tripping
        Assertions.assertEquals(nck28, CookieUtil.valueOf(NewCookie.class,
                CookieUtil.toString(NewCookie.class, nck28)));
    }

    private boolean testCookie(Cookie ck, String name, String value,
            String path, String domain, int version) throws Exception {

        StringBuffer sb = new StringBuffer();
        boolean pass = true;

        if (name == "" || name == null) {
            pass = false;
            sb.append("Cookie's name is empty");
        } else if (!ck.getName().toLowerCase().equals(name)) {
            pass = false;
            sb.append("Failed name test.  Expect " + name + " got " +
                    ck.getName());
        }

        if (value == "" || value == null) {
            pass = false;
            sb.append("Cookie's value is empty");
        } else if (!ck.getValue().toLowerCase().equals(value)) {
            pass = false;
            sb.append("Failed value test.  Expect " + value + " got " +
                    ck.getValue());
        }

        if (ck.getVersion() != version) {
            pass = false;
            sb.append("Failed version test.  Expect " + version + " got " +
                    ck.getVersion());
        }

        if (path == "" || path == null) {
            if (ck.getPath() != "" && ck.getPath() != null) {
                pass = false;
                sb.append("Failed path test.  Expect null String, got " +
                        ck.getPath());
            }
        } else if (ck.getPath() == null || ck.getPath() == "") {
            pass = false;
            sb.append("Failed path test.  Got null, expecting " + path);
        } else if (!ck.getPath().toLowerCase().equals(path)) {
            pass = false;
            sb.append("Failed path test.  Expect " + path + " got " +
                    ck.getPath());
        }

        if (domain == "" || domain == null) {
            if (ck.getDomain() != "" && ck.getDomain() != null) {
                pass = false;
                sb.append("Failed path test.  Expect " + domain + " got " +
                        ck.getDomain());
            }
        } else if (!ck.getDomain().toLowerCase().equals(domain)) {
            pass = false;
            sb.append("Failed domain test.  Expect " + domain + " got " +
                    ck.getDomain());
        }

        if (!pass) {
            throw new Exception("At least one assertion falied: " + sb.toString());
        }

        return pass;
    }

    private boolean verifyNewCookie(NewCookie nck, String name, String value,
            String path, String domain, int version, String comment, int maxage,
            Date expiry, boolean secure, boolean httpOnly) throws Exception {

        StringBuffer sb = new StringBuffer();
        boolean pass = true;

        if (name == null || name.isEmpty()) {
            pass = false;
            sb.append("NewCookie's name is empty");
        } else if (!nck.getName().toLowerCase().equals(name)) {
            pass = false;
            sb.append("Failed name test.  Expect " + name + " got " +
                    nck.getName());
        }

        if (value == null || value.isEmpty()) {
            pass = false;
            sb.append("NewCookie's value is empty");
        } else if (!nck.getValue().toLowerCase().equals(value)) {
            pass = false;
            sb.append("Failed value test.  Expect " + value + " got " +
                    nck.getValue());
        }

        if (nck.getVersion() != version) {
            pass = false;
            sb.append("Failed version test.  Expect " + version + " got " +
                    nck.getVersion());
        }

        if (comment == null || comment.isEmpty()) {
            if (nck.getComment() != "" && nck.getComment() != null) {
                pass = false;
                sb.append("Failed comment test.  Expect null String, got <" +
                        nck.getComment() + ">");
            }
        } else if (!nck.getComment().toLowerCase().equals(comment)) {
            pass = false;
            sb.append("Failed comment test.  Expect " + comment + " got " +
                    nck.getComment());
        }

        if (path == null || path.isEmpty()) {
            if (nck.getPath() != "" && nck.getPath() != null) {
                pass = false;
                sb.append("Failed path test.  Expect null String, got " +
                        nck.getPath());
            }
        } else if (nck.getPath() == null || nck.getPath().isEmpty()) {
            pass = false;
            sb.append("Failed path test.  Got null, expecting " + path);
        } else if (!nck.getPath().toLowerCase().equals(path)) {
            pass = false;
            sb.append("Failed path test.  Expect " + path + " got " +
                    nck.getPath());
        }

        if (domain == null || domain.isEmpty()) {
            if (nck.getDomain() != null && !nck.getDomain().isEmpty()) {
                pass = false;
                sb.append("Failed domain test.  Expect " + domain + " got " +
                        nck.getDomain());
            }
        } else if (!nck.getDomain().toLowerCase().equals(domain)) {
            pass = false;
            sb.append("Failed domain test.  Expect " + domain + " got " +
                    nck.getDomain());
        }

        if (nck.getMaxAge() != maxage) {
            pass = false;
            sb.append("Failed maxage test.  Expect " + maxage + " got " +
                    nck.getMaxAge());
        }

        if (expiry == null) {
            if (nck.getExpiry() != null) {
                pass = false;
                sb.append("Failed expiry test.  Expect " + expiry + " got " +
                        nck.getExpiry());
            }
        } else if (nck.getExpiry() == null) {
            pass = false;
            sb.append("Failed expiry test.  Got null, expecting " + expiry);
        } else if (!nck.getExpiry().equals(expiry)) {
            pass = false;
            sb.append("Failed expirytest.  Expect " + expiry + " got " +
                    nck.getExpiry());
        }

        if (nck.isSecure() != secure) {
            pass = false;
            sb.append("Failed secure test.  Expect " + secure + " got " +
                    nck.isSecure());
        }

        if (nck.isHttpOnly() != httpOnly) {
            pass = false;
            sb.append("Failed httpOnly test.  Expect " + httpOnly + " got " +
                    nck.isHttpOnly());
        }

        if (!pass) {
            throw new Exception("At least one assertion failed: " + sb.toString());
        }

        return pass;
    }

    /**
     * @tpTestDetails Create an instance of Response using Response.ResponseBuilder.cookie(NewCookie).build()
     *                and verify that correct status code is returned
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void cookieTest5() throws Exception {
        StringBuffer sb = new StringBuffer();
        boolean pass = true;

        int status = 200;

        String name = "name_1";
        String value = "value_1";
        int maxage = jakarta.ws.rs.core.NewCookie.DEFAULT_MAX_AGE;
        int version = 1;
        boolean secure = false;

        Cookie ck1 = new Cookie.Builder(name)
                .value(value)
                .build();
        NewCookie nck1 = new NewCookie.Builder(ck1).build();

        name = "name_2";
        value = "value_2";
        String path = "/acme";
        String domain = "";

        Cookie ck2 = new Cookie.Builder(name)
                .value(value)
                .path(path)
                .domain(domain)
                .build();
        NewCookie nck2 = new NewCookie.Builder(ck2).build();

        name = "name_3";
        value = "value_3";
        path = "";
        domain = "y.x.foo.com";

        Cookie ck3 = new Cookie.Builder(name)
                .value(value)
                .path(path)
                .domain(domain)
                .build();
        NewCookie nck3 = new NewCookie.Builder(ck3).build();

        List<String> cookies = Arrays.asList(
                CookieUtil.toString(NewCookie.class, nck1).toLowerCase(),
                CookieUtil.toString(NewCookie.class, nck2).toLowerCase(),
                CookieUtil.toString(NewCookie.class, nck3).toLowerCase());

        Response resp = Response.status(status).cookie(nck1, nck2, nck3).build();
        String tmp = verifyResponse(resp, null, status, null, null, null, null,
                null, cookies);
        if (tmp.endsWith("false")) {
            pass = false;
        }
        sb.append(tmp + "\n");
        logger.info(sb.toString());
        if (!pass) {
            throw new Exception("AT least one assertion failed.");
        }
    }

    /**
     * @tpTestDetails Create an instance of Response using Response.ResponseBuilder.header(String, Object).build()
     *                and verify that correct status code is returned
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testHeader() throws Exception {
        StringBuffer sb = new StringBuffer();
        boolean pass = true;

        int status = 200;
        List<String> type = Arrays.asList("text/plain", "text/html");
        List<String> encoding = Arrays.asList("gzip", "compress");
        List<String> lang = Arrays.asList("en_US", "en_GB", "zh_CN");

        String name = "name_1";
        String value = "value_1";
        Cookie ck1 = new Cookie.Builder(name)
                .value(value)
                .build();
        NewCookie nck1 = new NewCookie.Builder(ck1).build();

        List<String> cookies = Arrays.asList(
                CookieUtil.toString(NewCookie.class, nck1).toLowerCase());

        Response resp = Response.status(status).header("Content-type",
                "text/plain").header("Content-type", "text/html").header("Content-Language", "en_US")
                .header("Content-Language", "en_GB").header("Content-Language",
                        "zh_CN")
                .header("Cache-Control", "no-transform").header("Set-Cookie", "name_1=value_1;version=1").build();
        String tmp = verifyResponse(resp, null, status, encoding, lang, type,
                null, null, cookies);
        if (tmp.endsWith("false")) {
            pass = false;
        }
        sb.append(tmp + newline);
        logger.info(sb.toString());
        if (!pass) {
            throw new Exception("AT least one assertion failed.");
        }
    }

    private static String newline = "\n";
    private static String indent = "\t";

    private String verifyResponse(Response resp, String content, int status,
            HashMap<String, String> expected_map) throws Exception {
        boolean pass = true;
        StringBuffer sb = new StringBuffer();

        sb.append("========== Verifying a Response with Map: " + newline);

        String entity = (String) resp.getEntity();

        if ((content == null) || (content == "")) {
            if (!(resp.getEntity() == null) || (resp.getEntity() == "")) {
                pass = false;
                sb.append(indent +
                        "Entity verification failed: expecting no content, got " +
                        (String) resp.getEntity() + newline);
            }
        } else if (!content.equals(((String) resp.getEntity()))) {
            pass = false;
            sb.append(indent + "Entity verification failed: expecting " +
                    content +
                    ", got " + (String) resp.getEntity() + newline);
        } else {
            sb.append(indent + "Correct content found in Response: " +
                    (String) resp.getEntity() + newline);
        }

        if (resp.getStatus() != status) {
            pass = false;
            sb.append(indent + "Status code verification failed: expecting " +
                    status +
                    ", got " + resp.getStatus() + newline);
        } else {
            sb.append(indent + "Correct status found in Response: " + status +
                    newline);
        }

        MultivaluedMap<String, Object> mvp = resp.getMetadata();
        if (expected_map == null) {
            sb.append(indent +
                    "No keys to verify or expected, but found the following keys in Response:" +
                    newline);
            for (String key : mvp.keySet()) {
                sb.append(indent + indent + "Key: " + key + "; " +
                        mvp.getFirst(key) + ";" + newline);
            }
        } else {
            for (String key_actual : mvp.keySet()) {
                sb.append(indent + "Response contains key: " +
                        key_actual + newline);
            }
            sb.append(indent + "Verifying the following keys in Response:" +
                    newline);
            String actual, expected = null;
            for (Map.Entry<String, String> entry : expected_map.entrySet()) {
                String key = entry.getKey();
                if (!mvp.containsKey(key)) {
                    pass = false;
                    sb.append(indent + indent + "Key: " + key +
                            " is not found in Response;" + newline);
                } else if (key.equalsIgnoreCase("last-modified")) {
                    sb.append(indent + indent +
                            "Key Last-Modified is found in response" +
                            newline);
                } else {
                    expected = entry.getValue().toLowerCase();
                    actual = mvp.getFirst(key).toString().toLowerCase();

                    if (actual.startsWith("\"") && actual.endsWith("\"")) {
                        actual = actual.substring(1, actual.length() - 1);
                    }

                    if (!actual.equals(expected)) {
                        pass = false;
                        sb.append(indent + indent + "Key: " + key +
                                " found in Response, but with different value;" +
                                newline);
                        sb.append(indent + indent + "Expecting " +
                                entry.getValue() +
                                "; got " + mvp.getFirst(key) + newline);
                    }
                    sb.append(indent + indent + "Processed key " + key +
                            " with expected value " +
                            entry.getValue() + newline);
                }
            }
        }
        sb.append(indent + pass);
        return sb.toString();
    }

    private String verifyResponse(Response resp, String content, int status,
            List<String> encoding, List<String> language, List<String> type,
            List<String> var, List<String> ccl, List<String> cookies)
            throws Exception {
        boolean pass = true;
        StringBuffer sb = new StringBuffer();

        sb.append("========== Verifying a Response: " + newline);

        String tmp = verifyResponse(resp, content, status, null);
        sb.append(indent + tmp + newline);
        if (tmp.endsWith("false")) {
            pass = false;
        }

        MultivaluedMap<java.lang.String, java.lang.Object> mvp = resp.getMetadata();

        for (Map.Entry<String, List<Object>> entry : mvp.entrySet()) {
            String key = entry.getKey();
            sb.append(indent + "Processing Key found in response: " + key + ": " +
                    entry.getValue() + "; " + newline);

            if (key.equalsIgnoreCase("Vary")) {
                for (String value : var) {
                    String actual = entry.getValue().toString().toLowerCase();
                    if (actual.indexOf(value) < 0) {
                        pass = false;
                        sb.append(indent + indent + "Expected header " + value +
                                " not set in Vary." + newline);
                    } else {
                        sb.append(indent + indent + "Found expected header " +
                                value + "." + newline);
                    }
                }
            }

            if (encoding != null) {
                if (key.toString().equalsIgnoreCase("Content-encoding")) {
                    for (Object enc : entry.getValue()) {
                        if (!encoding.contains(enc.toString().toLowerCase())) {
                            pass = false;
                            sb.append(indent + indent + "Encoding test failed: " +
                                    newline);
                        }
                    }
                }
            }

            if (language != null) {
                if (key.toString().equalsIgnoreCase("Content-language")) {
                    for (Object lang : entry.getValue()) {
                        if (!language.contains(lang.toString())) {
                            pass = false;
                            sb.append(indent + indent + "language test failed: " +
                                    lang +
                                    " is not expected in Response" + newline);
                            for (String tt : language) {
                                sb.append(indent + indent +
                                        "Expecting Content-Language " + tt +
                                        newline);
                            }
                        }
                    }
                }
            }

            if (type != null) {
                if (key.toString().equalsIgnoreCase("Content-Type")) {
                    for (Object lang : entry.getValue()) {
                        if (!type.contains(lang.toString().toLowerCase())) {
                            pass = false;
                            sb.append(indent + indent +
                                    "Content-Type test failed: " + lang +
                                    " is not expected in Response" + newline);
                        }
                    }
                }
            }

            if (ccl != null) {
                for (String tt : ccl) {
                    sb.append("Expecting Cache-Control " + tt + newline);
                }
                if (key.toString().equalsIgnoreCase("Cache-Control")) {
                    for (Object all_ccl : entry.getValue()) {
                        for (String cc : ccl) {
                            if (!(all_ccl.toString().toLowerCase().indexOf(cc.toLowerCase()) > -1)) {
                                pass = false;
                                sb.append(indent + indent +
                                        "Cache-Control test failed: " + cc +
                                        " is not found in Response." + newline);
                            }
                        }
                    }
                }
            }

            if (cookies != null) {
                for (String tt : cookies) {
                    sb.append(indent + indent + "EXpecting Set-Cookie" + tt +
                            newline);
                }
                if (key.toString().equalsIgnoreCase("Set-Cookie")) {
                    for (Object nck_actual : entry.getValue()) {
                        sb.append(indent + indent + "Processing " +
                                nck_actual.toString() +
                                newline);
                        String s = nck_actual.toString().toLowerCase().replace(" ", "");
                        if (!cookies.contains(s)) {
                            pass = false;
                            sb.append(indent + indent +
                                    "Set-Cookie test failed: " + nck_actual +
                                    " is not expected in Response." + newline);
                        } else {
                            sb.append(indent + indent + "Expected Set-Cookie: " +
                                    nck_actual +
                                    " is found in Response." + newline);
                        }
                    }
                }
            }
        }
        sb.append(indent + pass);

        return sb.toString();
    }
}
