package pathrule

import (
	"encoding/json"
	"errors"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"imuslab.com/zoraxy/mod/utils"
)

/*
	Pathrules.go

	This script handle advance path settings and rules on particular
	paths of the incoming requests
*/

type Options struct {
	Enabled      bool   //If the pathrule is enabled.
	ConfigFolder string //The folder to store the path blocking config files
}

type BlockingPath struct {
	UUID          string
	MatchingPath  string
	ExactMatch    bool
	StatusCode    int
	CustomHeaders http.Header
	CustomHTML    []byte
	Enabled       bool
	CaseSenitive  bool
}

type Handler struct {
	Options       *Options
	BlockingPaths []*BlockingPath
}

// Create a new path blocker handler
func NewPathRuleHandler(options *Options) *Handler {
	//Create folder if not exists
	if !utils.FileExists(options.ConfigFolder) {
		os.Mkdir(options.ConfigFolder, 0775)
	}

	//Load the configs from file
	//TODO

	return &Handler{
		Options:       options,
		BlockingPaths: []*BlockingPath{},
	}
}

func (h *Handler) ListBlockingPath() []*BlockingPath {
	return h.BlockingPaths
}

// Get the blocker from matching path (path match, ignore tailing slash)
func (h *Handler) GetPathBlockerFromMatchingPath(matchingPath string) *BlockingPath {
	for _, blocker := range h.BlockingPaths {
		if blocker.MatchingPath == matchingPath {
			return blocker
		} else if strings.TrimSuffix(blocker.MatchingPath, "/") == strings.TrimSuffix(matchingPath, "/") {
			return blocker
		}
	}

	return nil
}

func (h *Handler) GetPathBlockerFromUUID(UUID string) *BlockingPath {
	for _, blocker := range h.BlockingPaths {
		if blocker.UUID == UUID {
			return blocker
		}
	}

	return nil
}

func (h *Handler) AddBlockingPath(pathBlocker *BlockingPath) error {
	//Check if the blocker exists
	blockerPath := pathBlocker.MatchingPath
	targetBlocker := h.GetPathBlockerFromMatchingPath(blockerPath)
	if targetBlocker != nil {
		//Blocker with the same matching path already exists
		return errors.New("path blocker with the same path already exists")
	}

	h.BlockingPaths = append(h.BlockingPaths, pathBlocker)

	//Write the new config to file
	return h.SaveBlockerToFile(pathBlocker)
}

func (h *Handler) RemoveBlockingPathByUUID(uuid string) error {
	newBlockingList := []*BlockingPath{}
	for _, thisBlocker := range h.BlockingPaths {
		if thisBlocker.UUID != uuid {
			newBlockingList = append(newBlockingList, thisBlocker)
		}
	}

	if len(h.BlockingPaths) == len(newBlockingList) {
		//Nothing is removed
		return errors.New("given matching path blocker not exists")
	}

	h.BlockingPaths = newBlockingList

	return h.RemoveBlockerFromFile(uuid)
}

func (h *Handler) SaveBlockerToFile(pathBlocker *BlockingPath) error {
	saveFilename := filepath.Join(h.Options.ConfigFolder, pathBlocker.UUID)
	js, _ := json.MarshalIndent(pathBlocker, "", " ")
	return os.WriteFile(saveFilename, js, 0775)
}

func (h *Handler) RemoveBlockerFromFile(uuid string) error {
	expectedConfigFile := filepath.Join(h.Options.ConfigFolder, uuid)
	if !utils.FileExists(expectedConfigFile) {
		return errors.New("config file not found on disk")
	}

	return os.Remove(expectedConfigFile)
}

// Get all the matching blockers for the given URL path
// return all the path blockers and the max length matching rule
func (h *Handler) GetMatchingBlockers(urlPath string) ([]*BlockingPath, *BlockingPath) {
	urlPath = strings.TrimSuffix(urlPath, "/")
	matchingBlockers := []*BlockingPath{}
	var longestMatchingPrefix *BlockingPath = nil
	for _, thisBlocker := range h.BlockingPaths {
		if thisBlocker.Enabled == false {
			//This blocker is not enabled. Ignore this
			continue
		}

		incomingURLPath := urlPath
		matchingPath := strings.TrimSuffix(thisBlocker.MatchingPath, "/")

		if !thisBlocker.CaseSenitive {
			//This is not case sensitive
			incomingURLPath = strings.ToLower(incomingURLPath)
			matchingPath = strings.ToLower(matchingPath)
		}

		if matchingPath == incomingURLPath {
			//This blocker have exact url path match
			matchingBlockers = append(matchingBlockers, thisBlocker)
			if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
				longestMatchingPrefix = thisBlocker
			}
			continue
		}

		if !thisBlocker.ExactMatch && strings.HasPrefix(incomingURLPath, matchingPath) {
			//This blocker have prefix url match
			matchingBlockers = append(matchingBlockers, thisBlocker)
			if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
				longestMatchingPrefix = thisBlocker
			}
			continue
		}
	}

	return matchingBlockers, longestMatchingPrefix
}
