package serve

import (
  "html/template"
  "log"
  "os"
  "runtime"
  "strings"
  "time"

  "gitler.moe/suwako/gitlin/pages"
  "gitler.moe/suwako/gitlin/utils"

  "github.com/gofiber/fiber/v2"
  "github.com/gofiber/fiber/v2/middleware/compress"
  "github.com/gofiber/fiber/v2/middleware/limiter"
  //  For debugging purposes
  //  "github.com/gofiber/fiber/v2/middleware/logger"
  "github.com/gofiber/fiber/v2/middleware/recover"
  "github.com/gofiber/template/html"
)

func Serve(port string) {
  utils.LoadCnf()
  isdebug, ok := os.LookupEnv("GITLIN_DEBUG")
  engine := html.New("/var/gitlin/views", ".html")
  if !ok {
    isdebug = "0"
  }
  if isdebug != "0" {
    engine = html.New("./views", ".html")
  }

  engine.AddFunc(
    // Add unescape function. This is needed to render HTML from Markdown.
    "unescape", func(s string) template.HTML {
      return template.HTML(s)
    },
  )

  app := fiber.New(fiber.Config{
    Views:   engine,
    Prefork: false,
    AppName: "Gitlin",
    // kind of screwed up way to fix rate limits
    EnableTrustedProxyCheck: true,
    TrustedProxies:          []string{"0.0.0.0/0"},
    ProxyHeader:             fiber.HeaderXForwardedFor,
    ErrorHandler: func(ctx *fiber.Ctx, err error) error {
      code := fiber.StatusInternalServerError

      if e, ok := err.(*fiber.Error); ok {
        code = e.Code
      }
      link := strings.TrimPrefix(err.Error(), "Cannot GET ")
      err = ctx.Status(code).Render("error", fiber.Map{
        "error":  err,
        "ver":    utils.Ver,
        "ves":    utils.Ves,
        "link":   link,
      })
      if err != nil {
        return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
      }

      return nil
    },
  })

  // For debugging purposes
  // app.Use(logger.New(logger.Config{
  //  Format: "[${ip}]:${port} ${status} - ${method} ${path} ${queryParams}\n",
  // }))

  app.Use(compress.New(compress.Config{
    Level: compress.LevelBestSpeed, // 1
  }))

  app.Use(recover.New())

  ratelimiter := limiter.New(limiter.Config{
    Max:        5,
    Expiration: 5 * time.Minute,
    LimitReached: func(c *fiber.Ctx) error {
      return c.Status(429).Render("ratelimit_gt", fiber.Map{
        "Title":  "Rate limit exceeded",
        "Ver": utils.Ver,
        "Ves": utils.Ves,
      })
    },
  })

  staticConfig := fiber.Static{
    Compress: true,
    // Cache-Control: max-age=31536000
    MaxAge: 31536000,
  }

  proxying, ok := os.LookupEnv("GITLIN_PROXYING_ENABLED")
  if !ok {
    proxying = "true"
  }
  // add global headers
  app.Use(func(c *fiber.Ctx) error {
    c.Set("X-Frame-Options", "SAMEORIGIN")
    c.Set("X-XSS-Protection", "1; mode=block")
    c.Set("X-Content-Type-Options", "nosniff")
    c.Set("Referrer-Policy", "no-referrer")
    if proxying == "true" {
      c.Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; script-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; connect-src 'self';")
    } else {
      c.Set("Content-Security-Policy", "default-src 'self' https://camo.githubusercontent.com https://avatars.githubusercontent.com https://github.com https://raw.githubusercontent.com https://gist.github.com; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://camo.githubusercontent.com https://avatars.githubusercontent.com https://gist.github.com; font-src 'self'; connect-src 'self' https://camo.githubusercontent.com https://avatars.githubusercontent.com https://github.com https://raw.githubusercontent.com https://gist.github.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'")
    }
    c.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")

    return c.Next()
  })

  app.Get("/", pages.HandleIndex)
  if isdebug == "0" {
    app.Static("/css", "/var/gitlin/public/css", staticConfig)
    app.Static("/robots.txt", "/var/gitlin/public/robots.txt", staticConfig)
    app.Static("/favicon.ico", "/var/gitlin/public/assets/favicon.ico", staticConfig)
    app.Static("/logo.png", "/var/gitlin/public/assets/logo.png", staticConfig)
    app.Static("/git.png", "/var/gitlin/public/assets/git.png", staticConfig)
  } else {
    app.Static("/css", "./public/css", staticConfig)
    app.Static("/robots.txt", "./public/robots.txt", staticConfig)
    app.Static("/favicon.ico", "./public/assets/favicon.ico", staticConfig)
    app.Static("/logo.png", "./public/assets/logo.png", staticConfig)
    app.Static("/git.png", "./public/assets/git.png", staticConfig)
  }
  app.Get("/about", pages.HandleAbout)
  app.Get("/explore", ratelimiter, pages.HandleExplore)
  app.Get("/:user", pages.HandleUser)
  app.Get("/avatar/:id", func(c *fiber.Ctx) error {
    if proxying == "true" {
      utils.ProxyRequest(c, "https://avatars.githubusercontent.com/u/"+c.Params("id")+"?v=4")
      return nil
    } else {
      c.Redirect("https://avatars.githubusercontent.com/u/" + c.Params("id") + "?v=4")
      return nil
    }
  })
  app.Get("/:user/:repo", pages.HandleRepo)
  app.Get("/:user/:repo/info/+", func(c *fiber.Ctx) error {
    // This is needed but it looks like git doesn't send it. Or maybe i just shouldn't program at midnight
    param := "?service=git-upload-pack"
    if proxying == "true" {
      utils.ProxyRequest(c, "https://github.com/"+c.Params("user")+"/"+c.Params("repo")+".git/info/"+c.Params("+")+param)
      return nil
    } else {
      c.Redirect("https://github.com/" + c.Params("user") + "/" + c.Params("repo") + ".git/info/" + c.Params("+") + param)
      return nil
    }
  })
  app.Post("/:user/:repo/git-upload-pack", func(c *fiber.Ctx) error {
    if proxying == "true" {
      utils.ProxyRequest(c, "https://github.com/"+c.Params("user")+"/"+c.Params("repo")+".git/git-upload-pack")
      return nil
    } else {
      c.Redirect("https://github.com/" + c.Params("user") + "/" + c.Params("repo") + ".git/git-upload-pack")
      return nil
    }
  })
  app.Get("/:user/:repo/blob/:branch/+", pages.FileView)
  app.Get("/:user/:repo/tree/:branch/+", pages.DirView)
  app.Get("/:user/:repo/tree/:branch", pages.HandleRepo)
  app.Get("/:user/:repo/archive/:branch", func(c *fiber.Ctx) error {
    if proxying == "true" {
      utils.ProxyRequest(c, "https://github.com/"+c.Params("user")+"/"+c.Params("repo")+"/archive/"+c.Params("branch"))
      return nil
    } else {
      c.Redirect("https://github.com/" + c.Params("user") + "/" + c.Params("repo") + "/archive/" + c.Params("branch"))
      return nil
    }
  })
  app.Get("/raw/:user/:repo/:branch/+", func(c *fiber.Ctx) error {
    if proxying == "true" {
      utils.ProxyRequest(c, "https://raw.githubusercontent.com/"+c.Params("user")+"/"+c.Params("repo")+"/"+c.Params("branch")+"/"+c.Params("+"))
      return nil
    } else {
      c.Redirect("https://raw.githubusercontent.com/" + c.Params("user") + "/" + c.Params("repo") + "/" + c.Params("branch") + "/" + c.Params("+"))
      return nil
    }
  })
  app.Get("/camo/:p1/:p2", func(c *fiber.Ctx) error {
    if proxying == "true" {
      utils.ProxyRequest(c, "https://camo.githubusercontent.com/"+c.Params("p1")+"/"+c.Params("p2"))
      return nil
    } else {
      c.Redirect("https://camo.githubusercontent.com/" + c.Params("p1") + "/" + c.Params("p2"))
      return nil
    }
  })
  app.Get("/gist/:user/:gistID", pages.HandleGist)
  app.Get("/download/gist/:user/:gistID/:revision", func(c *fiber.Ctx) error {
    if proxying == "true" {
      utils.ProxyRequest(c, "https://gist.github.com/"+c.Params("user")+"/"+c.Params("gistID")+"/archive/"+c.Params("revision")+".tar.gz")
      return nil
    } else {
      c.Redirect("https://gist.github.com/" + c.Params("user") + "/" + c.Params("gistID") + "/archive/" + c.Params("revision") + ".tar.gz")
      return nil
    }
  })
  app.Get("/:user/:repo/commits/:branch?", pages.HandleCommits)

  api := app.Group("/api")
  v1 := api.Group("/v1")
  v1.Get("/version", func(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{
      "version":      utils.Ver,
      "fiberversion": fiber.Version,
      "goversion":    runtime.Version(),
    })
  })

  val, ok := os.LookupEnv("GITLIN_PORT")
  if !ok {
    val = "9715"
  }
  if port != "" {
    val = port
  }
  log.Fatal(app.Listen(":" + val))
}
