/*
 * 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.server.directives;

import static org.apache.pekko.http.javadsl.server.PathMatchers.*;

import org.junit.Test;

import org.apache.pekko.http.javadsl.model.StatusCodes;
import org.apache.pekko.http.javadsl.model.headers.Host;
import org.apache.pekko.http.javadsl.server.PathMatchers;
import org.apache.pekko.http.javadsl.testkit.JUnitRouteTest;
import org.apache.pekko.http.javadsl.testkit.TestRoute;
import org.apache.pekko.http.scaladsl.model.HttpRequest;

public class PathDirectivesTest extends JUnitRouteTest {
  @Test
  public void testPathPrefixAndPath() {
    TestRoute route =
        testRoute(
            pathPrefix(
                "pet",
                () ->
                    concat(
                        path("cat", () -> complete("The cat!")),
                        path("dog", () -> complete("The dog!")),
                        pathSingleSlash(() -> complete("Here are only pets.")))));

    route.run(HttpRequest.GET("/pet/")).assertEntity("Here are only pets.");

    route
        .run(HttpRequest.GET("/pet")) // missing trailing slash
        .assertStatusCode(404);

    route.run(HttpRequest.GET("/pet/cat")).assertEntity("The cat!");

    route.run(HttpRequest.GET("/pet/dog")).assertEntity("The dog!");
  }

  @Test
  public void testRawPathPrefix() {
    TestRoute route1 =
        testRoute(rawPathPrefix(separateOnSlashes("/pet//cat"), () -> complete("The cat!")));

    route1.run(HttpRequest.GET("/pet//cat")).assertEntity("The cat!");

    // any suffix allowed
    route1.run(HttpRequest.GET("/pet//cat/abcdefg")).assertEntity("The cat!");

    TestRoute route2 =
        testRoute(
            rawPathPrefix(
                separateOnSlashes("/pet//cat"), () -> pathEnd(() -> complete("The cat!"))));

    route2.run(HttpRequest.GET("/pet//cat")).assertEntity("The cat!");

    route2.run(HttpRequest.GET("/pet//cat/abcdefg")).assertStatusCode(404);
  }

  @Test
  public void testSegment() {
    TestRoute route = testRoute(pathPrefix("hey", () -> path(name -> complete(name))));

    route.run(HttpRequest.GET("/hey/jude")).assertEntity("jude");
  }

  @Test
  public void testPathEnd() {
    TestRoute route =
        testRoute(
            pathPrefix(
                "test",
                () -> concat(pathEnd(() -> complete("end")), path("abc", () -> complete("abc")))));

    route.run(HttpRequest.GET("/test")).assertEntity("end");

    route.run(HttpRequest.GET("/test/abc")).assertEntity("abc");

    route.run(HttpRequest.GET("/xyz")).assertStatusCode(404);
  }

  @Test
  public void testSingleSlash() {
    TestRoute route = testRoute(pathPrefix("test", () -> pathSingleSlash(() -> complete("Ok"))));

    route.run(HttpRequest.GET("/test/")).assertEntity("Ok");

    route.run(HttpRequest.GET("/test")).assertStatusCode(404);
  }

  @Test
  public void testPathEndOrSingleSlash() {
    TestRoute route =
        testRoute(pathPrefix("test", () -> pathEndOrSingleSlash(() -> complete("Ok"))));

    route.run(HttpRequest.GET("/test")).assertEntity("Ok");

    route.run(HttpRequest.GET("/test/")).assertEntity("Ok");

    route.run(HttpRequest.GET("/abc")).assertStatusCode(404);
  }

  @Test
  public void testRawPathPrefixTest() {
    TestRoute route =
        testRoute(
            rawPathPrefixTest(
                slash().concat(segment("abc")), () -> extractUnmatchedPath(s -> complete(s))));

    route.run(HttpRequest.GET("/abc")).assertEntity("/abc");

    route.run(HttpRequest.GET("/abc/def")).assertEntity("/abc/def");

    route.run(HttpRequest.GET("/abcd/ef")).assertEntity("/abcd/ef");

    route.run(HttpRequest.GET("/xyz/def")).assertStatusCode(404);
  }

  @Test
  public void testPathPrefixTest() {
    TestRoute route =
        testRoute(pathPrefixTest("abc", () -> extractUnmatchedPath(s -> complete(s))));

    route.run(HttpRequest.GET("/abc")).assertEntity("/abc");

    route.run(HttpRequest.GET("/abc/def")).assertEntity("/abc/def");

    route.run(HttpRequest.GET("/abcd/ef")).assertEntity("/abcd/ef");

    route.run(HttpRequest.GET("/xyz/def")).assertStatusCode(404);
  }

  @Test
  public void testPathSuffix() {
    TestRoute route =
        testRoute(
            pathSuffix(
                PathMatchers.slash().concat(segment("abc")),
                () -> extractUnmatchedPath(s -> complete(s))));

    route.run(HttpRequest.GET("/test/abc/")).assertEntity("/test/");

    route.run(HttpRequest.GET("/abc/")).assertEntity("/");

    route.run(HttpRequest.GET("/abc/def")).assertStatusCode(404);

    route.run(HttpRequest.GET("/abc")).assertStatusCode(404);
  }

  @Test
  public void testPathSuffixTest() {
    TestRoute route =
        testRoute(pathSuffixTest("abc", () -> extractUnmatchedPath(s -> complete(s))));

    route.run(HttpRequest.GET("/test/abc")).assertEntity("/test/abc");

    route.run(HttpRequest.GET("/abc")).assertEntity("/abc");

    route.run(HttpRequest.GET("/abc/def")).assertStatusCode(404);
  }

  @Test
  public void testIntegerMatcher() {
    TestRoute route =
        testRoute(
            path(segment("age").slash(integerSegment()), value -> complete(value.toString())));

    route.run(HttpRequest.GET("/age/38")).assertEntity("38");

    route.run(HttpRequest.GET("/age/abc")).assertStatusCode(404);
  }

  @Test
  public void testIntegerConcatMatcher() {
    TestRoute route =
        testRoute(
            // this is testing that we can express the same path using slash() and concat() as with
            // nexting inside slash()
            path(
                segment("age").slash().concat(integerSegment()),
                value -> complete(value.toString())));

    route.run(HttpRequest.GET("/age/38")).assertEntity("38");

    route.run(HttpRequest.GET("/age/abc")).assertStatusCode(404);
  }

  @Test
  public void testTwoVals() {
    // tests that `x` and `y` have different identities which is important for
    // retrieving the values

    TestRoute route =
        testRoute(
            path(
                segment("multiply").slash(integerSegment()).slash("with").slash(integerSegment()),
                (x, y) -> complete(String.format("%d * %d = %d", x, y, x * y))));

    route.run(HttpRequest.GET("/multiply/3/with/6")).assertEntity("3 * 6 = 18");
  }

  @Test
  public void testHexIntegerMatcher() {
    TestRoute route =
        testRoute(
            path(segment("color").slash(hexIntegerSegment()), color -> complete(color.toString())));

    route.run(HttpRequest.GET("/color/a0c2ef")).assertEntity(Integer.toString(0xa0c2ef));
  }

  @Test
  public void testLongMatcher() {
    TestRoute route =
        testRoute(
            path(segment("bigage").slash(longSegment()), bigAge -> complete(bigAge.toString())));

    route.run(HttpRequest.GET("/bigage/12345678901")).assertEntity("12345678901");
  }

  @Test
  public void testSegmentMatcher() {
    TestRoute route =
        testRoute(path(segment("string").slash(segment()), bigAge -> complete(bigAge)));

    route.run(HttpRequest.GET("/string/hello-it-is-me")).assertEntity("hello-it-is-me");
  }

  @Test
  public void testHexLongMatcher() {
    TestRoute route =
        testRoute(path(segment("code").slash(hexLongSegment()), code -> complete(code.toString())));

    route.run(HttpRequest.GET("/code/a0b1c2d3e4f5")).assertEntity(Long.toString(0xa0b1c2d3e4f5L));
  }

  @Test
  public void testRemainingMatcher() {
    TestRoute route = testRoute(path(remaining(), remainingPath -> complete(remainingPath)));

    route
        .run(HttpRequest.GET("/pets/afdaoisd/asda/sfasfasf/asf"))
        .assertEntity("pets/afdaoisd/asda/sfasfasf/asf");
  }

  @Test
  public void testRemainingMatcherInsidePath() {
    TestRoute route =
        testRoute(
            pathPrefix("pets", () -> path(remaining(), remainingPath -> complete(remainingPath))));

    route
        .run(HttpRequest.GET("/pets/afdaoisd/asda/sfasfasf/asf"))
        .assertEntity("afdaoisd/asda/sfasfasf/asf");
  }

  @Test
  public void testUUIDMatcher() {
    TestRoute route =
        testRoute(path(segment("by-uuid").slash(uuidSegment()), uuid -> complete(uuid.toString())));

    route
        .run(HttpRequest.GET("/by-uuid/6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
        .assertEntity("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
  }

  @Test
  public void testSegmentsMatcher() {
    TestRoute route =
        testRoute(
            path(segment("pets").slash(segments()), segments -> complete(segments.toString())));

    route.run(HttpRequest.GET("/pets/cat/dog")).assertEntity("[cat, dog]");
  }

  @Test
  public void testRedirectToTrailingSlashIfMissing() {
    TestRoute route =
        testRoute(redirectToTrailingSlashIfMissing(StatusCodes.FOUND, () -> complete("Ok")));

    route
        .run(HttpRequest.GET("/home").addHeader(Host.create("example.com")))
        .assertStatusCode(302)
        .assertHeaderExists("Location", "http://example.com/home/");

    route.run(HttpRequest.GET("/home/")).assertStatusCode(200).assertEntity("Ok");
  }

  @Test
  public void testRedirectToNoTrailingSlashIfPresent() {
    TestRoute route =
        testRoute(redirectToNoTrailingSlashIfPresent(StatusCodes.FOUND, () -> complete("Ok")));

    route
        .run(HttpRequest.GET("/home/").addHeader(Host.create("example.com")))
        .assertStatusCode(302)
        .assertHeaderExists("Location", "http://example.com/home");

    route.run(HttpRequest.GET("/home")).assertStatusCode(200).assertEntity("Ok");
  }

  @Test
  public void testIgnoreTrailingSlash() {
    TestRoute route =
        testRoute(
            ignoreTrailingSlash(
                () ->
                    concat(
                        path("foo", () -> complete("Ok")),
                        path("bar", () -> pathEndOrSingleSlash(() -> complete("Ok"))),
                        path(PathMatchers.segment("baz").slash(), () -> complete("Ok")))));

    route.run(HttpRequest.GET("/foo")).assertStatusCode(StatusCodes.OK).assertEntity("Ok");

    route.run(HttpRequest.GET("/foo/")).assertStatusCode(StatusCodes.OK).assertEntity("Ok");

    route.run(HttpRequest.GET("/bar/")).assertStatusCode(StatusCodes.OK).assertEntity("Ok");

    route.run(HttpRequest.GET("/baz")).assertStatusCode(StatusCodes.OK).assertEntity("Ok");

    route.run(HttpRequest.GET("/baz/")).assertStatusCode(StatusCodes.OK).assertEntity("Ok");

    route
        .run(HttpRequest.GET("/foo/?query#frag"))
        .assertStatusCode(StatusCodes.OK)
        .assertEntity("Ok");

    route.run(HttpRequest.GET("/foz/")).assertStatusCode(StatusCodes.NOT_FOUND);
  }
}
