/*
** Job Arranger for ZABBIX
** Copyright (C) 2025 Daiwa Institute of Research Ltd. All Rights Reserved.
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

package main

import (
	"fmt"
	"os"
	"regexp"
	"runtime/debug"
	"strconv"
	"strings"

	clientcommon "jobarranger2/src/jobarg_server/managers/icon_exec_manager/workers/common"
	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/server"
	"jobarranger2/src/libs/golibs/event"
	"jobarranger2/src/libs/golibs/utils"
)

var (
	executionData       common.IconExecutionProcessData
	IconIfData          common.IconIfData
	BeforeData          common.RunJobVariableTable
	JobData             common.RunJobTable
	err                 error
	exitCode            int
	eventData           common.EventData
	runStatusChangeFlag bool
	tmpDir, udsPath     string
)

const (
	JAFlowTypeTrue   = 1
	JAFlowTypeFalse  = 2
	JAFlowTypeNormal = 0
)

func ifIconClient() (int, error) {
	var (
		innerJobnetMainId, innerJobnetId, innerJobId uint64
		jobnetId                                     string
		runCount, methodFlag, iconType               int
		flag                                         bool
	)
	executionData, err = clientcommon.GetIconExecData()
	if err != nil {
		return common.JA_JOBEXEC_FAIL, err
	}

	// Check necessary data
	innerJobnetMainId = executionData.RunJobData.InnerJobnetMainID
	if innerJobnetMainId <= 0 {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid inner_jobnet_main_id")
	}
	innerJobId = executionData.RunJobData.InnerJobID
	if innerJobId <= 0 {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid inner_job_id")
	}
	runCount = executionData.RunJobData.RunCount
	if runCount < 0 {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid run_count")
	}
	methodFlag = int(executionData.RunJobData.MethodFlag)
	if methodFlag < 0 {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid method_flag")
	}

	// check flag file exist or not
	flag, err = event.CheckRunCountFile(clientcommon.RunCountFolderPath, innerJobnetMainId, innerJobId, runCount, methodFlag)
	if err != nil {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("failed to create flag file : %w", err)
	} else if !flag { // flag == false (nil && false)
		return common.JA_JOBEXEC_IGNORE, nil
	}

	err = utils.Convert(executionData.RunJobData.Data, &IconIfData)
	if err != nil {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("failed to convert Data : %w", err)
	}

	err = clientcommon.SetBeforeVariable(executionData)
	if err != nil {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("failed to set before variables: %w", err)
	}

	err = utils.Convert(executionData.RunJobData, &JobData)
	if err != nil {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("failed to convert Data : %w", err)
	}

	jobnetId = executionData.RunJobnetData.JobnetID
	if jobnetId == "" {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid jobnet_id")
	}
	innerJobnetId = executionData.RunJobData.InnerJobnetID
	if innerJobnetId <= 0 {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid inner_jobnet_id")
	}

	iconType = int(executionData.RunJobData.IconType)
	if iconType != int(common.IconTypeIf) {
		return common.JA_JOBEXEC_FAIL, fmt.Errorf("invalid icon_type for if icon")
	}

	eventData = clientcommon.IconRunDataPrep(string(clientcommon.IconClientIf), executionData)

	err = event.CreateNextEvent(eventData, innerJobnetId, jobnetId, innerJobId)
	if err != nil {
		return common.JA_JOBRESULT_PRE_EXEC_FAIL, err
	}
	runStatusChangeFlag = true
	// flag file creation
	err = event.CreateRunCountFile(clientcommon.RunCountFolderPath, innerJobnetMainId, innerJobId, runCount)
	if err != nil {
		return common.JA_JOBRESULT_FAIL, fmt.Errorf("failed to create flag file : %w", err)
	}

	match, err := getIfInfo()
	if err != nil {
		return common.JA_JOBRESULT_FAIL, fmt.Errorf("getIfInfo failed: %w", err)
	}
	// Determine flow type
	flowType := -1
	if match {
		flowType = JAFlowTypeTrue
	} else {
		flowType = JAFlowTypeFalse
	}

	if flowType == -1 {
		return common.JA_JOBRESULT_FAIL, fmt.Errorf("invalid flow type: -1 : %w", err)
	}

	if JobData.TestFlag == common.FlagOn {
		flowType = JAFlowTypeNormal
	}

	IconIfData.FlowType = common.FlowType(flowType)

	// Write to fd3
	if err := clientcommon.WriteStructToFD3(IconIfData); err != nil {
		fmt.Fprintln(os.Stderr, "Warning: failed to write to fd3:", err)

	}
	return common.JA_JOBRESULT_SUCCEED, nil
}

func getIfInfo() (bool, error) {
	if IconIfData.ValueName == "" || IconIfData.ComparisonValue == "" {
		return false, fmt.Errorf("IconIfData is incomplete: %+v", IconIfData)
	}

	envVars, err := clientcommon.ResolveEnvVars(executionData)
	if err != nil {
		return false, fmt.Errorf("failed to resolve env vars: %w", err)
	}
	val, ok := envVars[IconIfData.ValueName]
	if !ok {
		return false, fmt.Errorf("value name '%s' not found in resolved env vars", IconIfData.ValueName)
	}

	match := performIfComparison(IconIfData.HandFlag, val, IconIfData.ComparisonValue)
	return match, nil
}

func performIfComparison(handFlag int, value string, comparison string) bool {
	switch handFlag {
	case 0:
		// Number comparison (simple equality for now)
		return numberMatch(value, comparison)
	case 1:
		// Regex match
		re, err := regexp.Compile(comparison)
		if err != nil {
			fmt.Printf("Invalid regex: %s, err: %v\n", comparison, err)
			return false
		}
		return re.MatchString(value)
	default:
		fmt.Printf("Unknown handFlag: %d\n", handFlag)
		return false
	}
}

func isNumber(s string) bool {
	// clean the string by trimming all spaces, newlines

	re := regexp.MustCompile(`^[-]?[0-9]+\.?[0-9]*$`)
	return re.MatchString(s)
}

func numberMatch(valueStr, patternStr string) bool {
	// Handle missing or empty values
	if valueStr == "" || patternStr == "" {
		return false
	}

	// Remove all spaces
	valueStr = strings.ReplaceAll(valueStr, " ", "")
	valueStr = strings.TrimSpace(valueStr)
	patternStr = strings.ReplaceAll(patternStr, " ", "")

	if !isNumber(valueStr) {
		return false
	}

	value, err := strconv.ParseFloat(valueStr, 64)
	if err != nil {
		return false
	}

	// Split multiple conditions like ">10,<=20"
	patterns := strings.Split(patternStr, ",")

	for _, pattern := range patterns {
		if matchSingleComparison(value, pattern) {
			return true
		}
	}
	return false
}

func matchSingleComparison(value float64, pattern string) bool {
	// Comparison operators
	ops := []string{">=", "<=", "!=", "==", ">", "<", "="}

	for _, op := range ops {
		if strings.HasPrefix(pattern, op) {
			numStr := strings.TrimPrefix(pattern, op)
			compareTo, err := strconv.ParseFloat(numStr, 64)
			if err != nil {
				fmt.Printf("Invalid comparison value in pattern: %s\n", pattern)
				return false
			}
			switch op {
			case ">":
				return value > compareTo
			case ">=":
				return value >= compareTo
			case "<":
				return value < compareTo
			case "<=":
				return value <= compareTo
			case "==", "=":
				return value == compareTo
			case "!=":
				return value != compareTo
			}
		}
	}

	// Range support: e.g., 1-5
	if strings.Contains(pattern, "-") {
		parts := strings.SplitN(pattern, "-", 2)
		if len(parts) == 2 {
			start, err1 := strconv.ParseFloat(parts[0], 64)
			end, err2 := strconv.ParseFloat(parts[1], 64)
			if err1 == nil && err2 == nil && start <= end {
				return value >= start && value <= end
			}
		}
	}

	// No recognized operator: try simple equality
	compareTo, err := strconv.ParseFloat(pattern, 64)
	if err != nil {
		return false
	}
	return value == compareTo
}

func main() {

	//catch runtime panic errors
	defer func() {
		if r := recover(); r != nil {
			//output stacktrace
			fmt.Fprintf(os.Stderr, "[IfIconClient] Runtime panic error occurs in client. error : %s", string(debug.Stack()))
			if runStatusChangeFlag {
				os.Exit(common.JA_JOBRESULT_FAIL)
			}
			os.Exit(common.JA_JOBRESULT_PRE_EXEC_FAIL)
		}
	}()

	// add client_pid in .clientPID file
	pid := os.Getpid()
	err := common.SetClientPid(pid)
	if err != nil {
		fmt.Fprintf(os.Stderr, "[IfIconClient] %v", err)
		os.Exit(common.JA_JOBEXEC_FAIL)
	}

	err = clientcommon.ParseArgs()
	if err != nil {
		fmt.Fprintf(os.Stderr, "[IfIconClient] %v", err)
		os.Exit(common.JA_JOBEXEC_FAIL)
	}

	server.Options.UnixSockParentDir = clientcommon.UdsDirpath
	server.Options.TmpDir = clientcommon.TmpDirPath

	exitCode, err = ifIconClient()
	if err != nil {
		fmt.Fprintf(os.Stderr, "[IfIconClient] %v", err)

	}
	os.Exit(exitCode)
}
