/*
** 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"
	"time"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/database"
	"jobarranger2/src/libs/golibs/event"
	"jobarranger2/src/libs/golibs/logger/logger"
	"jobarranger2/src/libs/golibs/utils"
	wm "jobarranger2/src/libs/golibs/worker_manager"
)

// Iterate ja_2_run_action_table and dispatch files according to the data
func handleUserActions(db database.Database) {
	const fn = "handleUserActions"
	actionTable := "ja_2_run_action_table"
	logData := logger.Logging{}

	dbCon, err := db.GetConn()
	if err != nil {
		logger.JaLog("JARECOVERYINT200008", logData, fn, err.Error())
		return
	}

	logger.JaLog("JARECOVERYINT400001", logData, fn)

	for {
		select {
		case <-wm.Wm.Ctx.Done():
			logger.JaLog("JARECOVERYINT400013", logData, fn, HandleUserActionsWorkerID)
			return
		case <-time.After(1 * time.Second):
			wm.Wm.MonitorChan <- HandleUserActionsWorkerID
		}

		dbResult, err := dbCon.Select(`
			SELECT id, inner_jobnet_id, inner_job_id, scheduled_time, action_flag, status 
			FROM %s WHERE status = 0
			ORDER BY created_date ASC
		`, actionTable)
		if err != nil {
			logger.JaLog("JARECOVERYINT200001", logData, fn, err.Error())
			continue
		}

		for dbResult.HasNextRow() {
			row, err := dbResult.Fetch()
			if err != nil {
				logger.JaLog("JARECOVERYINT200002", logData, fn, err.Error())
				break
			}

			logger.JaLog("JARECOVERYINT400002", logData, fn, fmt.Sprintf("%v", row))
			var runAction RunAction
			err = utils.MapStringStringToStruct(row, &runAction)
			if err != nil {
				logger.JaLog("JARECOVERYINT200003", logData, fn, err.Error())
				continue
			}

			// Fetch jobnet_id
			jobnetID, err := getJobnetID(runAction.InnerJobnetID, dbCon)
			if err != nil {
				logger.JaLog("JARECOVERYINT200009", logData, fn, runAction.InnerJobnetID)
				continue
			}
			logger.JaLog("JARECOVERYINT400003", logData, fn, jobnetID, runAction.InnerJobnetID)

			runAction.JobnetID = jobnetID

			// Process each row depending on method flag
			err = processUserAction(&runAction)
			if err != nil {
				logger.JaLog("JARECOVERYINT200004", logData, fn, fmt.Sprintf("%v", runAction), err.Error())
				continue
			}
			logger.JaLog("JARECOVERYINT000001", logData, fn, runAction.ActionFlag, runAction.JobnetID, runAction.InnerJobnetID, runAction.InnerJobID)

			// Update the processed row: status => 1
			err = dbCon.Begin()
			if err != nil {
				logger.JaLog("JARECOVERYINT200005", logData, fn, err.Error())
				continue
			}
			logger.JaLog("JARECOVERYINT400004", logData, fn)

			query := fmt.Sprintf(`
				UPDATE %s SET status = %d, update_date = %d WHERE id = %d
			`, actionTable, 1, time.Now().Unix(), runAction.ID)

			logger.JaLog("JARECOVERYINT400014", logData, fn, query)
			_, err = dbCon.Execute(query)

			if err != nil {
				logger.JaLog("JARECOVERYINT200006", logData, fn, err.Error(), runAction.ID)
				continue
			}

			err = dbCon.Commit()
			if err != nil {
				logger.JaLog("JARECOVERYINT200007", logData, fn, err.Error())
				continue
			}
			logger.JaLog("JARECOVERYINT400004", logData, fn)

		}
		dbResult.Free()

		time.Sleep(1 * time.Second)

	}
}

func getJobnetID(innerJobnetID uint64, dbCon database.DBConnection) (string, error) {
	logData := logger.Logging{}
	fn := "getJobnetID"
	jobnetTable := "ja_2_run_jobnet_table"
	logger.JaLog("JARECOVERYINT400015", logData, fn, innerJobnetID)

	dbResult, err := dbCon.Select(`
			SELECT jobnet_id 
			FROM %s WHERE inner_jobnet_id = %d
		`, jobnetTable, innerJobnetID)
	if err != nil {
		return "", fmt.Errorf("failed to select rows from table %s for inner_jobnet_id %d: %v", jobnetTable, innerJobnetID, err)
	}

	row, err := dbResult.Fetch()
	if err != nil {
		return "", fmt.Errorf("failed to fetch row from table %s for inner_jobnet_id %d: %v", jobnetTable, innerJobnetID, err)
	}

	jobnetID := row["jobnet_id"]
	if jobnetID == "" {
		return "", fmt.Errorf("empty jobnet_id")
	}

	return jobnetID, nil

}

func processUserAction(runAction *RunAction) error {
	fn := "processUserAction"
	logData := logger.Logging{
		InnerJobnetID: runAction.InnerJobnetID,
		InnerJobID:    runAction.InnerJobID,
	}
	logger.JaLog("JARECOVERYINT400006", logData, fn, fmt.Sprintf("%v", *runAction))

	userAction := common.UserAction(runAction.ActionFlag)

	switch userAction {
	case common.ActionIconUnhold: // to Flow Manager
		// Event Data preparation
		return prepareEventData(common.EventIconUnhold, *runAction, common.FlowManagerProcess, common.FlowProcessData{
			InnerJobnetId: runAction.InnerJobnetID,
			InnerJobId:    runAction.InnerJobID,
			JobnetId:      runAction.JobnetID,
		})

	case common.ActionIconRunErrSkip: // to Jobnet Manager
		return prepareEventData(common.EventIconRunErrSkip, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId: runAction.InnerJobnetID,
			InnerJobId:    runAction.InnerJobID,
			JobnetID:      runAction.JobnetID,
		})

	case common.ActionIconHoldSkip: // to Flow Manager
		return prepareEventData(common.EventIconHoldSkip, *runAction, common.FlowManagerProcess, common.FlowProcessData{
			InnerJobnetId: runAction.InnerJobnetID,
			InnerJobId:    runAction.InnerJobID,
			JobnetId:      runAction.JobnetID,
		})

	case common.ActionIconStop: // to Flow Manager
		// Event Data preparation
		return prepareEventData(common.EventIconStop, *runAction, common.FlowManagerProcess, common.FlowProcessData{
			InnerJobnetId: runAction.InnerJobnetID,
			InnerJobId:    runAction.InnerJobID,
			JobnetId:      runAction.JobnetID,
		})

	case common.ActionIconRerun: // to JobnetManager
		// Event Data preparation
		return prepareEventData(common.EventIconRerunStatusSync, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId: runAction.InnerJobnetID,
			InnerJobId:    runAction.InnerJobID,
			JobnetID:      runAction.JobnetID,
		})

	case common.ActionJobnetStop: // to Jobnet Manager
		// Event Data preparation
		return prepareEventData(common.EventJobnetStop, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId: runAction.InnerJobnetID,
			JobnetID:      runAction.JobnetID,
		})

	case common.ActionDelayedStart: // to Jobnet Manager
		// Event Data preparation
		return prepareEventData(common.EventDelayedStart, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId: runAction.InnerJobnetID,
		})

	case common.ActionScheduleUpdate: // to JobnetManager
		return prepareEventData(common.EventScheduleUpdate, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId: runAction.InnerJobnetID,
			ScheduledTime: runAction.ScheduledTime,
		})

	case common.ActionJobnetHold:
		return prepareEventData(common.EventJobnetHold, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId:    runAction.InnerJobnetID,
			StartPendingFlag: StartPendingFlagOn,
		})

	case common.ActionJobnetUnhold:
		return prepareEventData(common.EventJobnetUnhold, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId:    runAction.InnerJobnetID,
			StartPendingFlag: StartPendingFlagOff,
		})

	case common.ActionScheduleDelete:
		// Event Data preparation
		return prepareEventData(common.EventScheduleDelete, *runAction, common.JobnetManagerProcess, common.JobnetRunData{
			InnerJobnetId: runAction.InnerJobnetID,
		})

	default:
		// Invalid user action flag
		return fmt.Errorf("invalid user action flag: %d", userAction)
	}
}

func prepareEventData[T any](eventName common.EventName, runAction RunAction, procType common.ProcessType, nextProcData T) error {
	fn := "prepareEventData"
	logData := logger.Logging{}

	// Event Data preparation
	var eventData common.EventData
	eventData.Event.Name = eventName
	eventData.Event.UniqueKey = common.GetUniqueKey(common.RecoveryManagerProcess)
	eventData.NextProcess.Name = procType
	eventData.NextProcess.Data = nextProcData

	// Create next event
	err := event.CreateNextEvent(eventData, runAction.InnerJobnetID, runAction.JobnetID, runAction.InnerJobID)
	if err != nil {
		return fmt.Errorf("create event failed: %v", err)
	}

	logger.JaLog("JARECOVERYINT000002", logData, fn, fmt.Sprintf("%v", runAction))

	return nil
}
