package main

import (
	"errors"
	"fmt"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"slices"
	"time"

	"github.com/georgysavva/scany/v2/pgxscan"
	"github.com/labstack/echo/v4"
)

// Upload saves a file to the storage directory.
func Upload(file *multipart.FileHeader, from_admin bool) (string, error) {
	log.Printf("Uploading file: %s", file.Filename)
	log.Printf("DB instance: %v", Db.Dbpool)
	var newId string
	err := Db.Dbpool.QueryRow(Db.Ctx, "INSERT INTO public.storageindex(filename)VALUES (null) RETURNING id;").Scan(&newId)
	if err != nil {
		log.Fatal(err)
	}
	if newId == "" {
		return "", errors.New("failed to create new id")
	}
	err = Storage.Add(file, newId)
	if err != nil {
		return "", err
	}
	//if !from_admin , expires in 60 days
	var expiresAt *time.Time
	if !from_admin {
		exp := time.Now().AddDate(0, 2, 0) // 60 days
		expiresAt = &exp
	} else {
		expiresAt = nil
	}

	_, err = Db.Dbpool.Exec(Db.Ctx, "UPDATE public.storageindex set filename= $2, ext = $3, from_admin = $4, expires_at = $5 where id = $1", newId, file.Filename, filepath.Ext(file.Filename), from_admin, expiresAt)
	if err != nil {
		log.Fatal(err)
	}

	_, err = Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE filename IS NULL")
	if err != nil {
		log.Fatal(err)
	}
	return newId, nil

}

func StorageAuth(c echo.Context) error {
	token := c.Request().URL.Query().Get("token")
	if len(token) < 1 {
		return c.String(http.StatusUnauthorized, "Unauthorized")
	}
	var tk []string
	err := pgxscan.Select(Db.Ctx, Db.Dbpool, &tk, "SELECT token FROM public.temp_tokens WHERE token = $1 and elapse > $2", token, time.Now())
	if err != nil {
		return err
	}
	if tk == nil {
		return errors.New("invalid token")
	}

	_, err = Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.temp_tokens WHERE elapse < $1", time.Now())
	if err != nil {
		return err
	}
	return nil
}

func RemoveFromDb(ref string) error {

	_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", ref)
	if err != nil {
		return err
	}
	return nil
}

func RemoveExpired() error {
	//get all expired files
	var items_db []Storageindex
	err := pgxscan.Select(Db.Ctx, Db.Dbpool, &items_db, "SELECT * FROM public.storageindex WHERE expires_at is not null and expires_at < NOW()")
	if err != nil {
		return fmt.Errorf("failed to get expired items: %w", err)
	}
	for _, item := range items_db {
		formattedName := item.Id + item.Ext
		err = Storage.Delete(formattedName)
		if err != nil {
			return fmt.Errorf("failed to delete file %s: %w", formattedName, err)
		}
		err = RemoveFromDb(item.Id)
		if err != nil {
			return fmt.Errorf("failed to remove item from db: %w", err)
		}

	}
	return nil
}

func CheckConcruency() error {

	var items_db []struct {
		Id  string
		Ext string
	}

	err := pgxscan.Select(Db.Ctx, Db.Dbpool, &items_db, "SELECT id, ext FROM public.storageindex")
	if err != nil {
		return err
	}

	files, err := Storage.List()
	if err != nil {
		return err
	}
	if len(items_db) != len(files) {
		var founds []string
		for _, item := range items_db {
			formattedName := item.Id + item.Ext
			found := slices.Contains(files, formattedName)
			if found {
				founds = append(founds, formattedName)
			} else {

				_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", item.Id)
				if err != nil {
					return err
				}
			}

		}
		for _, file := range files {
			found := slices.Contains(founds, file)
			if !found {
				fmt.Printf("File %v not found in db\n", file)
				//TODO decidere se eliminarlo

			}
		}
	}

	return nil
}

func TempFolderCleaner() error {
	files, err := os.ReadDir(filepath.Join(storagepath, "/tmp/"))
	if err != nil {
		return err
	}
	fmt.Println("Temp files: ", len(files))
	if len(files) >= tempStorageLimit {

		var oldest_Time time.Time
		var oldest_File string
		for _, file := range files {
			file_infos, err := file.Info()
			if err != nil {
				return err
			}
			if oldest_Time.IsZero() || file_infos.ModTime().Before(oldest_Time) {
				oldest_Time = file_infos.ModTime()
				oldest_File = file.Name()
			}
		}
		err = os.Remove(filepath.Join(storagepath, "/tmp/", oldest_File))
		if err != nil {
			return err
		}

	}
	return nil

}

// ROUTES
func StorageRoutes(e *echo.Echo) {

	e.GET("/storage/list", func(c echo.Context) error {
		filenames, err := Storage.List()
		if err != nil {
			return c.String(http.StatusInternalServerError, err.Error())
		}
		return c.JSON(http.StatusOK, filenames)
	})

	e.GET("/storage/get/:filename", func(c echo.Context) error {
		err := TempFolderCleaner()
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to clean temp folder: "+err.Error())
		}
		err = StorageAuth(c)
		if err != nil {
			return c.JSON(http.StatusUnauthorized, "Unauthorized: "+err.Error())
		}

		filename := c.Param("filename")
		path, err := Storage.Get(filename)
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to get file: "+err.Error())
		}

		return c.File(path)
	})
	e.POST("/storage/upload", func(c echo.Context) error {
		fmt.Println("Uploading file")

		err := StorageAuth(c)
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to verify Auth: "+err.Error())
		}

		from_admin := c.QueryParam("from_admin") == "true"
		file, err := c.FormFile("file")
		if err != nil {
			return err
		}

		fileId, err := Upload(file, from_admin)
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to upload file: "+err.Error())
		}

		return c.String(http.StatusOK, fileId)
	})
	e.DELETE("/storage/delete/:filename", func(c echo.Context) error {
		err := StorageAuth(c)
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to verify Auth: "+err.Error())
		}
		filename := c.Param("filename")

		err = Storage.Delete(filename)
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to remove file / file not found: "+err.Error())
		}
		return c.String(http.StatusOK, "File removed")

	})
	e.GET("/storage/remove-expired", func(c echo.Context) error {

		err := RemoveExpired()
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to remove files / files not found: "+err.Error())
		}
		return c.String(http.StatusOK, "File removed")

	})
	e.GET("/storage/test", func(c echo.Context) error {
		err := CheckConcruency()
		if err != nil {
			return c.String(http.StatusInternalServerError, "Failed to check concruency: "+err.Error())
		}
		return c.String(http.StatusOK, "OK")
	})
}
