package routes

import (
	"bytes"
	"fmt"
	"illusionman1212/kotapi-go/db"
	"illusionman1212/kotapi-go/models"
	"image/jpeg"
	"image/png"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/edwvee/exiffix"
	"github.com/gorilla/mux"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

func RandomHandler(w http.ResponseWriter, req *http.Request) {
	var kot models.Kot

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Methods", "GET")

	max, err := db.Kots.EstimatedDocumentCount(db.Ctx)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An error has occurred while querying the database", "status": 500, "failed": true }`))
		return
	}

	min := 1

	rand.Seed(time.Now().UnixNano())

	num := rand.Intn(int(max+1)-min) + min

	err = db.Kots.FindOne(db.Ctx, bson.M{"id": num}).Decode(&kot)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		}
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{
		"id": "` + strconv.Itoa(int(kot.MyID)) + `",
		"url": "` + kot.Url + `",
		"compressed_url": "` + kot.CompressedUrl + `",
		"status": 200,
		"failed": false
	}`))
}

func IdHandler(w http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)
	var kot models.Kot

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Methods", "GET")

	id, err := strconv.Atoi(params["id"])
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{ "error": "Invalid or incomplete request","status": 400, "failed": true }`))
		return
	}

	err = db.Kots.FindOne(db.Ctx, bson.M{"id": id}).Decode(&kot)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		}
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{
		"id": "` + strconv.Itoa(int(kot.MyID)) + `",
		"url": "` + kot.Url + `",
		"compressed_url": "` + kot.CompressedUrl + `",
		"status": 200,
		"failed": false
	}`))
}

func AddKotHandler(w http.ResponseWriter, req *http.Request) {
	var kot models.Kot

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Methods", "POST")

	req.ParseMultipartForm(10 << 20)

	file, handler, err := req.FormFile("image")
	if err != nil {
		log.Print("no image was uploaded")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{ "error": "Invalid or incomplete request","status": 400, "failed": true }`))
		return
	}

	password := req.Form.Get("password")
	if password == "" {
		log.Print("no password was sent")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{ "error": "Invalid or incomplete request","status": 400, "failed": true }`))
		return
	}
	if password != os.Getenv("PASSWORD") {
		log.Print("password is incorrect")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{ "error": "Not authorized","status": 401, "failed": true }`))
		return
	}

	filename := make([]byte, 16)
	rand.Seed(time.Now().UnixNano())
	_, err = rand.Read(filename)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		return
	}

	extensions := strings.Split(handler.Filename, ".")
	extension := strings.ToLower(extensions[len(extensions)-1])

	defer file.Close()

	data := make([]byte, handler.Size)
	file.Read(data)
	compressedData := data

	if extension == "png" {
		enc := &png.Encoder{
			CompressionLevel: png.BestCompression,
		}
		buf := &bytes.Buffer{}
		decodedImage, err := png.Decode(bytes.NewReader(data))
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An error occurred while compressing the image","status": 500, "failed": true }`))
			return
		}
		enc.Encode(buf, decodedImage)
		compressedData = buf.Bytes()
	} else if extension == "jpeg" || extension == "jpg" {
		decodedImage, _, err := exiffix.Decode(bytes.NewReader(data))
		buf := &bytes.Buffer{}
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An error occurred while compressed the image","status": 500, "failed": true }`))
			return
		}

		jpeg.Encode(buf, decodedImage, &jpeg.Options{Quality: 75})
		compressedData = buf.Bytes()
	}

	os.Mkdir("./kots", 0755)
	os.Mkdir("./kots/compressed", 0755)
	newFile, err := os.Create(fmt.Sprintf("./kots/%x.%s", filename, extension))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An error occurred while writing the file","status": 500, "failed": true }`))
		return
	}

	defer newFile.Close()
	newFile.Write(data)

	compressedNewFile, err := os.Create(fmt.Sprintf("./kots/compressed/%x.%s", filename, extension))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An error occurred while writing compressed file","status": 500, "failed": true }`))
		return
	}

	defer compressedNewFile.Close()
	compressedNewFile.Write(compressedData)

	count, err := db.Kots.EstimatedDocumentCount(db.Ctx)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		return
	}

	kot.ID = primitive.NewObjectIDFromTimestamp(time.Now())
	kot.MyID = int32(count + 1)
	kot.Url = fmt.Sprintf("%s/kots/%x.%s", os.Getenv("BASE_URL"), filename, extension)
	kot.CompressedUrl = fmt.Sprintf("%s/kots/compressed/%x.%s", os.Getenv("BASE_URL"), filename, extension)

	_, err = db.Kots.InsertOne(db.Ctx, kot)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		return
	}
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{ "message": "Successfully welcomed a new kot :)" "status": 200, "failed": false }`))
}

func KotsHandler(w http.ResponseWriter, req *http.Request) {
	filename := mux.Vars(req)
	filepath := fmt.Sprintf("./kots/%s", filename["filename"])
	_, err := os.Stat(filepath)
	if err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		return
	}
	http.ServeFile(w, req, filepath)
}

func KotsCompressedHandler(w http.ResponseWriter, req *http.Request) {
	filename := mux.Vars(req)
	filepath := fmt.Sprintf("./kots/compressed/%s", filename["filename"])
	_, err := os.Stat(filepath)
	if err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		return
	}
	http.ServeFile(w, req, filepath)
}
package routes

import (
	"bytes"
	"fmt"
	"illusionman1212/kotapi-go/db"
	"illusionman1212/kotapi-go/models"
	"image/jpeg"
	"image/png"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/edwvee/exiffix"
	"github.com/gorilla/mux"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

func RandomHandler(w http.ResponseWriter, req *http.Request) {
	var kot models.Kot

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Methods", "GET")

	max, err := db.Kots.EstimatedDocumentCount(db.Ctx)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An error has occurred while querying the database", "status": 500, "failed": true }`))
		return
	}

	// ids start from 1
	min := 1
	// seed the default source using the current time in unix nanosecond format
	rand.Seed(time.Now().UnixNano())
	// generate random number between min and max [min, max]
	num := rand.Intn(int(max+1)-min) + min

	// find the random kot document and store it in the `kot` variable
	err = db.Kots.FindOne(db.Ctx, bson.M{"id": num}).Decode(&kot)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		}
		return
	}

	// create a json from the kot struct variable and then write it as a response
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{
		"id": "` + strconv.Itoa(int(kot.MyID)) + `",
		"url": "` + kot.Url + `",
		"compressed_url": "` + kot.CompressedUrl + `",
		"status": 200,
		"failed": false
	}`))
}

func IdHandler(w http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)
	var kot models.Kot

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Methods", "GET")

	id, err := strconv.Atoi(params["id"])
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{ "error": "Invalid or incomplete request","status": 400, "failed": true }`))
		return
	}

	err = db.Kots.FindOne(db.Ctx, bson.M{"id": id}).Decode(&kot)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		}
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{
		"id": "` + strconv.Itoa(int(kot.MyID)) + `",
		"url": "` + kot.Url + `",
		"compressed_url": "` + kot.CompressedUrl + `",
		"status": 200,
		"failed": false
	}`))
}

func AddKotHandler(w http.ResponseWriter, req *http.Request) {
	var kot models.Kot

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Methods", "POST")

	// 10 MB file size limit
	req.ParseMultipartForm(10 << 20)

	file, handler, err := req.FormFile("image")
	if err != nil {
		log.Print("no image was uploaded")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{ "error": "Invalid or incomplete request","status": 400, "failed": true }`))
		return
	}

	password := req.Form.Get("password")
	if password == "" {
		log.Print("no password was sent")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(`{ "error": "Invalid or incomplete request","status": 400, "failed": true }`))
		return
	}
	if password != os.Getenv("PASSWORD") {
		log.Print("password is incorrect")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{ "error": "Not authorized","status": 401, "failed": true }`))
		return
	}

	filename := make([]byte, 16)
	rand.Seed(time.Now().UnixNano())
	_, err = rand.Read(filename)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		return
	}

	extensions := strings.Split(handler.Filename, ".")
	extension := strings.ToLower(extensions[len(extensions)-1])

	defer file.Close()

	data := make([]byte, handler.Size)
	file.Read(data)
	compressedData := data

	if extension == "png" {
		enc := &png.Encoder{
			CompressionLevel: png.BestCompression,
		}
		buf := &bytes.Buffer{}
		decodedImage, err := png.Decode(bytes.NewReader(data))
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An error occurred while compressing the image","status": 500, "failed": true }`))
			return
		}
		enc.Encode(buf, decodedImage)
		compressedData = buf.Bytes()
	} else if extension == "jpeg" || extension == "jpg" {
		decodedImage, _, err := exiffix.Decode(bytes.NewReader(data))
		buf := &bytes.Buffer{}
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(`{ "error": "An error occurred while compressed the image","status": 500, "failed": true }`))
			return
		}

		jpeg.Encode(buf, decodedImage, &jpeg.Options{Quality: 75})
		compressedData = buf.Bytes()
	}

	os.Mkdir("./kots", 0755)
	os.Mkdir("./kots/compressed", 0755)
	newFile, err := os.Create(fmt.Sprintf("./kots/%x.%s", filename, extension))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An error occurred while writing the file","status": 500, "failed": true }`))
		return
	}

	defer newFile.Close()
	newFile.Write(data)

	compressedNewFile, err := os.Create(fmt.Sprintf("./kots/compressed/%x.%s", filename, extension))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An error occurred while writing compressed file","status": 500, "failed": true }`))
		return
	}

	defer compressedNewFile.Close()
	compressedNewFile.Write(compressedData)

	count, err := db.Kots.EstimatedDocumentCount(db.Ctx)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		return
	}

	kot.ID = primitive.NewObjectIDFromTimestamp(time.Now())
	kot.MyID = int32(count + 1)
	kot.Url = fmt.Sprintf("%s/kots/%x.%s", os.Getenv("BASE_URL"), filename, extension)
	kot.CompressedUrl = fmt.Sprintf("%s/kots/compressed/%x.%s", os.Getenv("BASE_URL"), filename, extension)

	_, err = db.Kots.InsertOne(db.Ctx, kot)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{ "error": "An unknown error has occurred","status": 500, "failed": true }`))
		return
	}
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{ "message": "Successfully welcomed a new kot :)" "status": 200, "failed": false }`))
}

func KotsHandler(w http.ResponseWriter, req *http.Request) {
	filename := mux.Vars(req)
	filepath := fmt.Sprintf("./kots/%s", filename["filename"])
	_, err := os.Stat(filepath)
	if err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		return
	}
	http.ServeFile(w, req, filepath)
}

func KotsCompressedHandler(w http.ResponseWriter, req *http.Request) {
	filename := mux.Vars(req)
	filepath := fmt.Sprintf("./kots/compressed/%s", filename["filename"])
	_, err := os.Stat(filepath)
	if err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{ "error": "Not found","status": 404, "failed": true }`))
		return
	}
	http.ServeFile(w, req, filepath)
}
