package loghandler

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"jannex/log-manager/modules/cache"
	"jannex/log-manager/modules/config"
	"jannex/log-manager/modules/structs"
	"jannex/log-manager/modules/utils"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

var FileMutexMap = make(map[string]*sync.Mutex)
var FileMutexMapLock sync.Mutex

func getFileMutex(filePath string) *sync.Mutex {
	FileMutexMapLock.Lock()
	defer FileMutexMapLock.Unlock()

	mutex, ok := FileMutexMap[filePath]

	if !ok {
		mutex = &sync.Mutex{}
		FileMutexMap[filePath] = mutex
	}

	return mutex
}

func AddLog(body structs.LogBody) {
	year, month, day := time.Now().Date()
	date := strconv.Itoa(day) + "-" + strconv.Itoa(int(month)) + "-" + strconv.Itoa(year)
	logFolder := config.Cfg.LogFolder

	utils.CreateDirectoryIfNotExists(logFolder + body.Type)

	path := logFolder + body.Type + "/" + date + ".log"

	// get the mutex for this file
	mutex := getFileMutex(path)

	// lock the mutex to ensure the file is only opened once at a time
	mutex.Lock()
	defer mutex.Unlock()

	file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

	if err != nil {
		fmt.Println(err)
		return
	}

	defer file.Close()

	for _, log := range body.Logs {
		if _, err := fmt.Fprintln(file, log); err != nil {
			fmt.Println(err)
		}
	}

	marshaledLogs, err := json.Marshal(body.Logs)

	if err != nil {
		fmt.Println(err)
		return
	}

	for clientId, sseClient := range cache.GetSSEClients() {
		if sseClient.LogType == body.Type && sseClient.Date == date {
			sseClient.MessageChannel <- structs.SSEClientChannelMessage{
				ClientId: clientId,
				Message:  marshaledLogs,
			}
		}
	}
}

func GetAvailableLogFiles(logType string) ([]string, error) {
	var availableLogs []string

	path := config.Cfg.LogFolder + logType

	files, err := os.ReadDir(path)

	if err != nil {
		fmt.Println(err)
		return []string{}, err
	}

	// sort the files by date
	sort.Slice(files, func(i, j int) bool {
		dateFormat := "2-1-2006"
		dateA, _ := time.Parse(dateFormat, strings.Split(files[i].Name(), ".")[0])
		dateB, _ := time.Parse(dateFormat, strings.Split(files[j].Name(), ".")[0])
		return dateA.After(dateB)
	})

	for _, file := range files {
		availableLogs = append(availableLogs, strings.Split(file.Name(), ".")[0])
	}

	return availableLogs, nil
}

func GetLogByDate(logType string, date string) ([]string, error) {
	var logs []string

	path := config.Cfg.LogFolder + logType + "/" + date + ".log"

	// get the mutex for this file
	mutex := getFileMutex(path)

	// lock the mutex to ensure the file is only opened once at a time
	mutex.Lock()
	defer mutex.Unlock()

	file, err := os.Open(path)
	if err != nil {
		fmt.Println(err)
		return []string{}, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		logs = append(logs, scanner.Text())
	}

	if len(logs) == 0 {
		return []string{}, nil
	}

	return logs, nil
}

func GetAvailableLogTypes() []string {
	var availableLogTypes []string

	files, err := os.ReadDir(config.Cfg.LogFolder)

	if err != nil {
		fmt.Println(err)
		return []string{}
	}

	for _, file := range files {
		availableLogTypes = append(availableLogTypes, file.Name())
	}

	if len(availableLogTypes) == 0 {
		return []string{}
	}

	return availableLogTypes
}

func StartBackgroundLogDeleter() {
	ticker := time.NewTicker(24 * time.Hour)

	for range ticker.C {
		CheckForDeletableLogs()
	}
}

func CheckForDeletableLogs() {
	daysToKeepLogs := config.Cfg.DaysToKeepLogs

	logFolder := config.Cfg.LogFolder

	err := filepath.Walk(logFolder, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Println(err)
			return err
		}

		if !info.IsDir() {
			// check if the file is older than the daysToKeepLogs
			// get the date from the file name
			date := strings.Split(info.Name(), ".")[0]

			dateFormat := "2-1-2006"

			fileDate, err := time.Parse(dateFormat, date)

			if err != nil {
				fmt.Println(err)
				return err
			}

			// get the date from the daysToKeepLogs
			daysToKeepLogsDate := time.Now().AddDate(0, 0, -daysToKeepLogs)

			// compare the dates
			if fileDate.Before(daysToKeepLogsDate) {
				// delete the file
				err := os.Remove(path)

				if err != nil {
					fmt.Println(err)
					return err
				}

				AddLog(structs.LogBody{
					Type: "system",
					Logs: []string{"I " + utils.GetTime() + "LogManager: Deleted log file " + path + " because it was older than " + strconv.Itoa(daysToKeepLogs) + " days."},
				})
			}
		} else {
			// Check if the directory is empty.
			isEmpty, err := isDirEmpty(path)

			if err != nil {
				fmt.Printf("Error checking %s: %v\n", path, err)
				return err
			}

			if isEmpty {
				fmt.Printf("Deleting empty directory: %s\n", path)

				if err := os.Remove(path); err != nil {
					fmt.Printf("Error deleting %s: %v\n", path, err)
				}

				AddLog(structs.LogBody{
					Type: "system",
					Logs: []string{"I " + utils.GetTime() + "LogManager: Deleted empty log folder " + path},
				})
			}
		}

		return nil
	})

	if err != nil {
		fmt.Println(err)
	}
}

func isDirEmpty(dirPath string) (bool, error) {
	dir, err := os.Open(dirPath)

	if err != nil {
		return false, err
	}

	defer dir.Close()

	_, err = dir.Readdirnames(1) // Try to read an entry from the directory.

	if err == nil {
		// There is at least one entry in the directory; it's not empty.
		return false, nil
	}

	if err == io.EOF {
		// The directory is empty.
		return true, nil
	}

	return false, err
}
