/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.http.javadsl.model;

import org.apache.pekko.http.scaladsl.model.IllegalUriException;
import org.apache.pekko.japi.Pair;
import org.junit.Test;
import org.scalatestplus.junit.JUnitSuite;

import java.util.Optional;
import static org.junit.Assert.assertEquals;

public class UriTest extends JUnitSuite {

  @Test
  public void testValidUrlExamples() {
    // #valid-uri-examples
    Uri uri1 = Uri.create("ftp://ftp.is.co.za/rfc/rfc1808.txt");
    assertEquals("ftp", uri1.getScheme());
    assertEquals(Host.create("ftp.is.co.za"), uri1.getHost());
    assertEquals("/rfc/rfc1808.txt", uri1.getPathString());

    Uri uri2 = Uri.create("http://www.ietf.org/rfc/rfc2396.txt");
    assertEquals("http", uri2.getScheme());
    assertEquals(Host.create("www.ietf.org"), uri2.getHost());
    assertEquals("/rfc/rfc2396.txt", uri2.getPathString());

    Uri uri3 = Uri.create("ldap://[2001:db8::7]/c=GB?objectClass?one");
    assertEquals("ldap", uri3.getScheme());
    assertEquals(Host.create("[2001:db8::7]"), uri3.getHost());
    assertEquals("objectClass?one", uri3.query().toString());

    Uri uri4 = Uri.create("mailto:John.Doe@example.com");
    assertEquals("mailto", uri4.getScheme());
    assertEquals("John.Doe@example.com", uri4.getPathString());

    Uri uri5 = Uri.create("news:comp.infosystems.www.servers.unix");
    assertEquals("news", uri5.getScheme());
    assertEquals("comp.infosystems.www.servers.unix", uri5.getPathString());

    Uri uri6 = Uri.create("tel:+1-816-555-1212");
    assertEquals("tel", uri6.getScheme());
    assertEquals("+1-816-555-1212", uri6.getPathString());

    Uri uri7 = Uri.create("telnet://192.0.2.16:80/");
    assertEquals("telnet", uri7.getScheme());
    assertEquals(Host.create("192.0.2.16"), uri7.getHost());
    assertEquals("/", uri7.getPathString());

    Uri uri8 = Uri.create("urn:oasis:names:specification:docbook:dtd:xml:4.1.2");
    assertEquals("urn", uri8.getScheme());
    assertEquals("oasis:names:specification:docbook:dtd:xml:4.1.2", uri8.getPathString());
    // #valid-uri-examples
  }

  @Test
  public void testPercentEscape() {
    // #dont-double-decode
    Uri uri1 = Uri.create("http://foo.com?foo=%2520");
    assertEquals(Optional.of("%20"), uri1.query().get("foo"));
    Uri uri2 = Uri.create("http://foo.com?foo=%2F%5C");
    assertEquals(Optional.of("/\\"), uri2.query().get("foo"));
    // #dont-double-decode
  }

  @Test
  public void testAddingQueryParams() {
    // #create-uri-with-query
    Uri uri = Uri.create("http://foo.com").query(Query.create(Pair.create("foo", "bar")));
    // #create-uri-with-query
    assertEquals(Optional.of("foo=bar"), uri.rawQueryString());
  }

  // #illegal-scheme
  @Test(expected = IllegalUriException.class)
  public void testIllegalScheme() {
    Uri.create("foö:/a");
    // IllegalUriException(
    //  "Illegal URI reference: Invalid input 'ö', expected scheme-char, 'EOI', '#', ':', '?',
    // slashSegments or pchar (line 1, column 3)",
    //  "http://user:ö@host\n" +
    //  "            ^"
    // )
  }
  // #illegal-scheme

  // #illegal-userinfo
  @Test(expected = IllegalUriException.class)
  public void testIllegalUserInfo() {
    Uri.create("http://user:ö@host");
    // IllegalUriException(
    //  "Illegal URI reference: Invalid input 'ö', expected userinfo-char, pct-encoded, '@' or port
    // (line 1, column 13)",
    //  "http://use%2G@host\n" +
    //  "            ^"
    // )
  }
  // #illegal-userinfo

  // #illegal-percent-encoding
  @Test(expected = IllegalUriException.class)
  public void testIllegalPercentEncoding() {
    Uri.create("http://use%2G@host");
    // IllegalUriException(
    //  "Illegal URI reference: Invalid input 'G', expected HEXDIG (line 1, column 13)",
    //  "http://www.example.com/name with spaces/\n" +
    //  "                           ^"
    // )
  }
  // #illegal-percent-encoding

  // #illegal-path
  @Test(expected = IllegalUriException.class)
  public void testIllegalPath() {
    Uri.create("http://www.example.com/name with spaces/");
    // IllegalUriException(
    //  "Illegal URI reference: Invalid input ' ', expected '/', 'EOI', '#', '?' or pchar (line 1,
    // column 28)",
    //  "http://www.example.com/name with spaces/\n" +
    //  "                           ^"
    // )
  }
  // #illegal-path

  // #illegal-path-with-control-char
  @Test(expected = IllegalUriException.class)
  public void testIllegalPathWithControlCharacter() {
    Uri.create("http:///with\newline");
    // IllegalUriException(
    //  "Illegal URI reference: Invalid input '\\n', expected '/', 'EOI', '#', '?' or pchar (line 1,
    // column 13)",
    //  "http:///with\n" +
    //  "            ^"
    // )
  }
  // #illegal-path-with-control-char

  @Test(expected = IllegalUriException.class)
  public void testIllegalQuery() {
    // #illegal-query
    Uri.create("?a%b=c").query();
    // IllegalUriException(
    //  " Illegal query: Invalid input '=', expected HEXDIG (line 1, column 4): a%b=c",
    //  "a%b=c\n" +
    //  " ^"
    // )
    // #illegal-query
  }

  // #query-strict-definition
  public Query strict(String query) {
    return Query.create(query, org.apache.pekko.http.javadsl.model.Uri.STRICT);
  }
  // #query-strict-definition

  @Test
  public void testStrictMode() {
    // #query-strict-mode
    // query component (name: "a", and value: "b") is equal to parsed query string "a=b"
    assertEquals(Query.create(Pair.create("a", "b")), strict("a=b"));

    assertEquals(Query.EMPTY, strict(""));
    assertEquals(Query.create(Pair.create("a", "")), strict("a"));
    assertEquals(Query.create(Pair.create("a", "")), strict("a="));
    assertEquals(Query.create(Pair.create("a", " ")), strict("a=+"));
    assertEquals(Query.create(Pair.create("a", "+")), strict("a=%2B"));
    assertEquals(Query.create(Pair.create("", "a")), strict("=a"));
    assertEquals(Query.create(Pair.create("a", "")).withParam("", ""), strict("a&"));
    assertEquals(Query.create(Pair.create("a", "b")), strict("a=%62"));

    assertEquals(Query.create(Pair.create("a=b", "c")), strict("a%3Db=c"));
    assertEquals(Query.create(Pair.create("a&b", "c")), strict("a%26b=c"));
    assertEquals(Query.create(Pair.create("a+b", "c")), strict("a%2Bb=c"));
    assertEquals(Query.create(Pair.create("a;b", "c")), strict("a%3Bb=c"));

    assertEquals(Query.create(Pair.create("a", "b=c")), strict("a=b%3Dc"));
    assertEquals(Query.create(Pair.create("a", "b&c")), strict("a=b%26c"));
    assertEquals(Query.create(Pair.create("a", "b+c")), strict("a=b%2Bc"));
    assertEquals(Query.create(Pair.create("a", "b;c")), strict("a=b%3Bc"));

    assertEquals(Query.create(Pair.create("a b", "c")), strict("a+b=c")); // '+' is parsed to ' '
    assertEquals(Query.create(Pair.create("a", "b c")), strict("a=b+c")); // '+' is parsed to ' '
    // #query-strict-mode

    // #query-strict-without-percent-encode
    assertEquals(Query.create(Pair.create("a?b", "c")), strict("a?b=c"));
    assertEquals(Query.create(Pair.create("a/b", "c")), strict("a/b=c"));

    assertEquals(Query.create(Pair.create("a", "b?c")), strict("a=b?c"));
    assertEquals(Query.create(Pair.create("a", "b/c")), strict("a=b/c"));
    // #query-strict-without-percent-encode
  }

  // #query-strict-mode-exception-1
  @Test(expected = IllegalUriException.class)
  public void testStrictModeException1() {
    strict("a^=b");
    // IllegalUriException(
    //  "Illegal query: Invalid input '^', expected '+', '=', query-char, 'EOI', '&' or pct-encoded
    // (line 1, column 2)",
    //  "a^=b\n" +
    //  " ^")
  }
  // #query-strict-mode-exception-1

  // #query-strict-mode-exception-2
  @Test(expected = IllegalUriException.class)
  public void testStrictModeException2() {
    strict("a;=b");
    // IllegalUriException(
    //  "Illegal query: Invalid input ';', expected '+', '=', query-char, 'EOI', '&' or pct-encoded
    // (line 1, column 2)",
    //  "a;=b\n" +
    //  " ^")
  }
  // #query-strict-mode-exception-2

  // #query-strict-mode-exception-3
  @Test(expected = IllegalUriException.class)
  public void testStrictModeException3() {
    // double '=' in query string is invalid
    strict("a=b=c");
    // IllegalUriException(
    //  "Illegal query: Invalid input '=', expected '+', query-char, 'EOI', '&' or pct-encoded (line
    // 1, column 4)",
    //  "a=b=c\n"  +
    //  " ^")
  }
  // #query-strict-mode-exception-3

  // #query-strict-mode-exception-4
  @Test(expected = IllegalUriException.class)
  public void testStrictModeException4() {
    // following '%', it should be percent encoding (HEXDIG), but "%b=" is not a valid percent
    // encoding
    strict("a%b=c");
    // IllegalUriException(
    //  "Illegal query: Invalid input '=', expected HEXDIG (line 1, column 4)",
    //  "a%b=c\n" +
    //  "   ^")
  }
  // #query-strict-mode-exception-4

  // #query-relaxed-definition
  public Query relaxed(String query) {
    return Query.create(query, org.apache.pekko.http.javadsl.model.Uri.RELAXED);
  }
  // #query-relaxed-definition

  @Test
  public void testRelaxedMode() {
    // #query-relaxed-mode
    assertEquals(Query.EMPTY, relaxed(""));
    assertEquals(Query.create(Pair.create("a", "")), relaxed("a"));
    assertEquals(Query.create(Pair.create("a", "")), relaxed("a="));
    assertEquals(Query.create(Pair.create("a", " ")), relaxed("a=+"));
    assertEquals(Query.create(Pair.create("a", "+")), relaxed("a=%2B"));
    assertEquals(Query.create(Pair.create("", "a")), relaxed("=a"));
    assertEquals(Query.create(Pair.create("a", "")).withParam("", ""), relaxed("a&"));
    assertEquals(Query.create(Pair.create("a", "b")), relaxed("a=%62"));

    assertEquals(Query.create(Pair.create("a=b", "c")), relaxed("a%3Db=c"));
    assertEquals(Query.create(Pair.create("a&b", "c")), relaxed("a%26b=c"));
    assertEquals(Query.create(Pair.create("a+b", "c")), relaxed("a%2Bb=c"));
    assertEquals(Query.create(Pair.create("a;b", "c")), relaxed("a%3Bb=c"));

    assertEquals(Query.create(Pair.create("a", "b=c")), relaxed("a=b%3Dc"));
    assertEquals(Query.create(Pair.create("a", "b&c")), relaxed("a=b%26c"));
    assertEquals(Query.create(Pair.create("a", "b+c")), relaxed("a=b%2Bc"));
    assertEquals(Query.create(Pair.create("a", "b;c")), relaxed("a=b%3Bc"));

    assertEquals(Query.create(Pair.create("a b", "c")), relaxed("a+b=c")); // '+' is parsed to ' '
    assertEquals(Query.create(Pair.create("a", "b c")), relaxed("a=b+c")); // '+' is parsed to ' '
    // #query-relaxed-mode

    // #query-relaxed-without-percent-encode
    assertEquals(Query.create(Pair.create("a?b", "c")), relaxed("a?b=c"));
    assertEquals(Query.create(Pair.create("a/b", "c")), relaxed("a/b=c"));

    assertEquals(Query.create(Pair.create("a", "b?c")), relaxed("a=b?c"));
    assertEquals(Query.create(Pair.create("a", "b/c")), relaxed("a=b/c"));
    // #query-relaxed-without-percent-encode

    // #query-relaxed-mode-success
    assertEquals(Query.create(Pair.create("a^", "b")), relaxed("a^=b"));
    assertEquals(Query.create(Pair.create("a;", "b")), relaxed("a;=b"));
    assertEquals(Query.create(Pair.create("a", "b=c")), relaxed("a=b=c"));
    // #query-relaxed-mode-success
  }

  // #query-relaxed-mode-exception-1
  @Test(expected = IllegalUriException.class)
  public void testRelaxedModeException1() {
    // following '%', it should be percent encoding (HEXDIG), but "%b=" is not a valid percent
    // encoding
    // still invalid even in relaxed mode
    relaxed("a%b=c");
    // IllegalUriException(
    //  "Illegal query: Invalid input '=', expected '+', query-char, 'EOI', '&' or pct-encoded (line
    // 1, column 4)",
    //  "a%b=c\n" +
    //  "   ^")
  }
  // #query-relaxed-mode-exception-1

}
