package routes

import (
	"os"
	"shortener/database"
	"shortener/helpers"
	"strconv"
	"time"

	"github.com/asaskevich/govalidator"
	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
)

type request struct {
	URL         string        `json:"url"`
	CustomShort string        `json:"short"`
	Expiry      time.Duration `json:"expiry"`
}

type response struct {
	URL             string        `json:"url"`
	CustomShort     string        `json:"short"`
	Expiry          time.Duration `json:"expiry"`
	XRateRemaining  int           `json:"rate_limit"`
	XRateLimitReset time.Duration `json:"rate_limit_reset"`
}

func ShortenURL(c *fiber.Ctx) error {
	body := new(request)

	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": err.Error(),
		})
	}

	// Check user IP address and decrease the user rate remaining by 1
	// Limit the API call to maximum 10 times each 30 minutes per user
	// implement rate limiting
	r1 := database.CreateClient(1)
	defer r1.Close()

	val, err := r1.Get(database.Ctx, c.IP()).Result()

	if err == redis.Nil {
		_ = r1.Set(database.Ctx, c.IP(), os.Getenv("API_QUOTA"), time.Minute*30).Err()
	} else if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"message": err.Error(),
		})
	} else {
		valInt, _ := strconv.Atoi(val)
		if valInt <= 0 {
			limit, _ := r1.TTL(database.Ctx, c.IP()).Result()
			return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
				"message":          "Rate limit exceeded",
				"rate_limit_reset": limit / time.Nanosecond / time.Minute,
			})
		}
	}

	// Check if the input is an actual URL

	if !govalidator.IsURL(body.URL) {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Invalid URL " + body.URL,
		})
	}

	// Check for domain error
	if !helpers.RemoveDomainError(body.URL) {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"message": "You can't shorten this URL",
		})
	}

	// enforce https, SSL
	body.URL = helpers.EnforceHTTP(body.URL)

	var id string
	r := database.CreateClient(0)
	defer r.Close()

	if body.CustomShort == "" {
		maxTries, _ := strconv.Atoi(os.Getenv("MAX_TRY"))
		for maxTries > 0 {
			id = uuid.New().String()[:6]
			val, _ = r.Get(database.Ctx, id).Result()
			if val == "" {
				break
			}
			maxTries--
		}
	} else {
		id = body.CustomShort
		val, _ = r.Get(database.Ctx, id).Result()
	}

	// Check if shortened URL existed in DB
	// Expand expiration for existing pair (curstomShort, URL) if possible
	if val != "" && val != body.URL {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
			"error": "URL short is already in use. Please try again later",
		})
	}

	if body.Expiry <= 0 {
		body.Expiry = 24
	} else if body.Expiry > 720 {
		body.Expiry = 720
	}

	err = r.Set(database.Ctx, id, body.URL, body.Expiry*time.Hour).Err()

	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"message": "unable to connect to server",
		})
	}

	res := response{
		URL:             body.URL,
		CustomShort:     "",
		Expiry:          body.Expiry,
		XRateRemaining:  10,
		XRateLimitReset: 30,
	}

	r1.Decr(database.Ctx, c.IP())

	val, _ = r1.Get(database.Ctx, c.IP()).Result()
	res.XRateRemaining, _ = strconv.Atoi(val)

	ttl, _ := r1.TTL(database.Ctx, c.IP()).Result()
	res.XRateLimitReset = ttl / time.Nanosecond / time.Minute

	res.CustomShort = os.Getenv("DOMAIN") + "/" + id

	return c.Status(fiber.StatusOK).JSON(res)
}
