package main

import (
	"encoding/json"
	"fmt"
	"jobarranger2/src/libs/golibs/builder"
	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/database"
	"jobarranger2/src/libs/golibs/logger/logger"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

// This part is concerned with handling sql conditions in the transaction file
func handleSQLConditions(eventData *common.EventData, dbConn database.DBConnection, logData logger.Logging) error {
	fn := "handleSQLConditions"
	eventFilePath := eventData.Transfer.Files[0].Source
	filename := filepath.Base(eventFilePath)

	logger.JaLog("JADBSYNCER400019", logData, fn, filename, eventData.Event.Name)

	if len(eventData.SQLConditions) <= 0 {
		logger.JaLog("JADBSYNCER400020", logData, fn, filename, eventData.Event.Name)
		return nil
	}

	// need to handle sql conditions
	for _, sqlCondition := range eventData.SQLConditions {
		logger.JaLog("JADBSYNCER400023", logData, fn, filename, sqlCondition.SQL, sqlCondition.DefaultAction)

		// Get DB record from sql
		record, err := getValuesFromSQL(sqlCondition.SQL, dbConn)
		if err != nil {
			return err
		}

		logger.JaLog("JADBSYNCER400024", logData, fn, filename, sqlCondition.SQL, fmt.Sprintf("%+v", record))

		// check for conditions
		action, err := checkCondition(sqlCondition, record, logData, filename)
		if err != nil {
			return fmt.Errorf("failed to check condition: %v", err)
		}

		logger.JaLog("JADBSYNCER400028", logData, fn, filename, action, record)
		// perform the action
		switch action {
		case common.ActionWait:
			return recreateEvent(eventData, eventFilePath)
		case common.ActionExec:
			return nil
		case common.ActionIgnore:
			return handleActionIgnore(eventData, filename)
		case common.ActionError:
			return fmt.Errorf("condition check results in error action. db values: %v", record)
		}
	}

	return nil
}

func checkCondition(sqlCondition common.SQLCondition, dbRecord map[string]string, logData logger.Logging, filename string) (common.Action, error) {
	fn := "checkCondition"

	for _, condition := range sqlCondition.Conditions {
		matchCount := 0
		totalFields := len(condition.Fields)
		for column, field := range condition.Fields {
			logger.JaLog("JADBSYNCER400025", logData, fn, filename, column)
			dbValue, ok := dbRecord[column]
			if !ok {
				return sqlCondition.DefaultAction, fmt.Errorf("db column does not match the condition [%s]", column)
			}

			if compareCondition(dbValue, field, logData, filename) {
				matchCount++
			}
		}
		if matchCount == totalFields {
			return condition.Action, nil
		}
	}

	return sqlCondition.DefaultAction, nil
}

func compareCondition(dbValue string, field common.FieldCondition, logData logger.Logging, filename string) bool {
	fn := "compareCondition"

	logger.JaLog("JADBSYNCER400026", logData, fn, filename, dbValue, field.Values)
	for _, expected := range field.Values {
		logger.JaLog("JADBSYNCER400026", logData, fn, filename, dbValue, expected, field.Operator)

		switch ev := expected.(type) {
		case json.Number:
			logger.JaLog("JADBSYNCER400027", logData, fn, filename, expected, "json.Number")

			// Condition expects number → parse actual string into uint64
			dbValueUint, err := strconv.ParseUint(dbValue, 10, 64)
			if err != nil {
				logger.JaLog("JADBSYNCER200021", logData, fn, filename, fmt.Sprintf("%T", ev), err.Error())
				continue
			}

			condValue, err := strconv.ParseUint(ev.String(), 10, 64)
			if err != nil {
				logger.JaLog("JADBSYNCER200022", logData, fn, filename, ev.String(), err.Error())
				continue
			}

			matched, err := matchUint64(dbValueUint, condValue, field.Operator)
			if err != nil {
				logger.JaLog("JADBSYNCER200023", logData, fn, filename, dbValueUint, condValue, err.Error())
			}

			return matched
		case string:
			logger.JaLog("JADBSYNCER400027", logData, fn, filename, expected, "string")
			// Condition expects string → direct string compare
			matched, err := matchString(dbValue, ev, field.Operator)
			if err != nil {
				logger.JaLog("JADBSYNCER200023", logData, fn, filename, dbValue, ev, err.Error())
			}

			return matched
		default:
			logger.JaLog("JADBSYNCER200020", logData, fn, filename, fmt.Sprintf("%T", ev), expected)
		}
	}
	return false
}

func matchUint64(actual uint64, expected uint64, op common.Operator) (bool, error) {
	switch op {
	case common.OpEq:
		return actual == expected, nil
	case common.OpNe:
		return actual != expected, nil
	case common.OpLt:
		return actual < expected, nil
	case common.OpLe:
		return actual <= expected, nil
	case common.OpGt:
		return actual > expected, nil
	case common.OpGe:
		return actual >= expected, nil
	default:
		return false, fmt.Errorf("unsupported operator: %s", op)
	}
}

func matchString(actual string, expected string, op common.Operator) (bool, error) {
	switch op {
	case common.OpEq:
		return actual == expected, nil
	case common.OpNe:
		return actual != expected, nil
	default:
		return false, fmt.Errorf("unsupported operator: %s", op)
	}
}

func getValuesFromSQL(query string, dbConn database.DBConnection) (map[string]string, error) {
	dbResult, err := dbConn.Select(query)
	if err != nil {
		return nil, fmt.Errorf("failed to select values with query [%s]: %v", query, err)
	}

	defer dbResult.Free()

	if !dbResult.HasNextRow() {
		return nil, fmt.Errorf("record not found for query [%s]", query)
	}

	row, err := dbResult.Fetch()
	if err != nil {
		return nil, fmt.Errorf("failed to fetch for query [%s]: %v", query, err)
	}

	return row, nil

}

func recreateEvent(eventData *common.EventData, filePath string) error {
	// delay for event re-creation
	time.Sleep(100 * time.Millisecond)

	eventData.Event.UniqueKey = common.GetUniqueKey(common.DBSyncerManagerProcess)

	if err := builder.WriteJSONFile(filePath, *eventData); err != nil {
		return fmt.Errorf("failed to write event data to file '%s': %v", filePath, err)
	}

	// recreate the event
	tmpPath := strings.Replace(filePath, ".json", ".json.tmp", 1)
	if err := os.Rename(filePath, tmpPath); err != nil {
		return fmt.Errorf("failed to rename transaction file '%s' to tmp '%s': %w", filePath, tmpPath, err)
	}

	if err := os.Rename(tmpPath, filePath); err != nil {
		return fmt.Errorf("failed to rename transaction file '%s' back to json '%s': %w", tmpPath, filePath, err)
	}

	return &TimingError{
		ErrorMsg: fmt.Sprintf("event [%s] is retriggered", eventData.Event.Name),
	}

}

func handleActionIgnore(eventData *common.EventData, filename string) error {
	// Move to end folder
	moveFilesToDBSyncSubFolder(*eventData, logger.Logging{}, filename, EndFolder) // logs internally
	return &TimingError{
		ErrorMsg: fmt.Sprintf("ignoring the event [%s]", eventData.Event.Name),
	}

}
