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

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/database"
	"jobarranger2/src/libs/golibs/event"
	"jobarranger2/src/libs/golibs/utils"
)

func handleScheduledJobnet(
	ctx context.Context,
	scheduledTime int64,
	jobnetRunData common.JobnetRunData,
	currentFilePath string,
	nextEventData common.EventData,
) error {
	funcname := "handleScheduledJobnet"
	JobnetRunLogger.JaLog("JAJOBNETRUN400001", funcname)

	scheduled := time.Unix(scheduledTime, 0)
	JobnetRunLogger.JaLog("JAJOBNETRUN000002", scheduled.Format(time.RFC3339))
	duration := time.Until(scheduled)

	timer := time.NewTimer(duration)
	defer timer.Stop()

	select {
	case <-timer.C:
		// Update Jobnet summary after execution
		JobnetRunLogger.InnerJobnetID = jobnetRunData.InnerJobnetId
		JobnetRunLogger.InnerJobnetMainID = jobnetRunData.InnerJobnetId
		JobnetRunLogger.JobnetID = jobnetRunData.JobnetID
		JobnetRunLogger.JobnetName = jobnetRunData.JobnetName
		JobnetRunLogger.JaJobLog(common.JC_JOBNET_START)
		skipWaitEvent, moveFilePath, err := handleSkipAndWait(jobnetRunData.MultipleOptions, jobnetRunData.InnerJobnetId, jobnetRunData.JobnetID)
		if err != nil {
			JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
			return err
		}
		if moveFilePath == RUN {
			nextEventData.ParameterizedQueries = jaJobnetSummaryReady(jobnetRunData.InnerJobnetId, nextEventData.ParameterizedQueries...)
		} else {
			nextEventData = skipWaitEvent
		}

		err = MoveFileAndCreateEvent(jobnetRunData, currentFilePath, moveFilePath, nextEventData)
		if err != nil {
			JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
			return err
		}
	case <-ctx.Done():
		JobnetRunLogger.JaLog("JAJOBNETRUN400011", jobnetRunData.InnerJobnetId)
	}
	return nil
}

// runScheduledJobnetAsync runs scheduled jobnet in a safe goroutine
func runScheduledJobnetAsync(
	ctx context.Context,
	jobnetRunData common.JobnetRunData,
	currentFilePath string,
	nextEventData common.EventData,
) {
	funcname := "runScheduledJobnetAsync"
	cancelCtx, cancel := context.WithCancel(ctx)
	GetScheduleJobnets().Add(jobnetRunData.InnerJobnetId, cancel)

	go func() {
		defer GetScheduleJobnets().Remove(jobnetRunData.InnerJobnetId)
		JobnetRunLogger.JaLog("JAJOBNETRUN400010", jobnetRunData.InnerJobnetId)
		if err := handleScheduledJobnet(cancelCtx, int64(jobnetRunData.ScheduledTime), jobnetRunData, currentFilePath, nextEventData); err != nil {
			JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
		}

	}()
}

func processRun(jobnetPayload common.EventData, dbconn database.DBConnection) error {
	funcname := "processRun"
	JobnetRunLogger.JaLog("JAJOBNETRUN400001", funcname)
	var nextEventData common.EventData

	// Extract jobnet run data
	jobnetRunData, err := getJobnetRunData(jobnetPayload.NextProcess.Data)
	if err != nil {
		return fmt.Errorf("%s", err.Error())
	}

	// Handle start pending
	if jobnetRunData.StartPendingFlag == JA_SUMMARY_START_PENDING_ON {
		return nil
	}
	var currentFilePath string
	if jobnetPayload.Event.Name == common.EventStandAloneJob {
		currentFilePath = jobnetPayload.Transfer.Files[0].Source
	} else {
		currentFilePath = jobnetPayload.Transfer.Files[0].Destination
	}

	JobnetRunLogger.JaLog("JAJOBNETRUN400006", currentFilePath)
	GetScheduleJobnets().Remove(jobnetRunData.InnerJobnetId)

	queries := []common.ParameterizedQuery{}

	nextEventData, moveFilePath, err := prepareQueriesAndNextEvent(jobnetRunData, dbconn)
	if err != nil {
		JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
		return fmt.Errorf("%s", err.Error())
	}

	scheduled := time.Unix(int64(jobnetRunData.ScheduledTime), 0)
	// Handle delay start state
	if (jobnetRunData.RunType == JA_JOBNET_RUN_TYPE_SCHEDULED ||
		jobnetRunData.RunType == JA_JOBNET_RUN_TYPE_NORMAL) &&
		jobnetRunData.ScheduledTime != 0 &&
		time.Since(scheduled) > time.Minute &&
		jobnetRunData.StartPendingFlag != JA_SUMMARY_START_PENDING_OFF {

		JobnetRunLogger.JaLog("JAJOBNETRUN400004")

		JobnetRunLogger.InnerJobnetID = jobnetRunData.InnerJobnetId
		JobnetRunLogger.InnerJobnetMainID = jobnetRunData.InnerJobnetId
		JobnetRunLogger.JobnetID = jobnetRunData.JobnetID
		JobnetRunLogger.JobnetName = jobnetRunData.JobnetName
		JobnetRunLogger.JobnetStatus = common.StatusRunErr

		JobnetRunLogger.JaJobLog(common.JC_JOBNET_START_ERR)
		queries = jaSetStatusJobnet(jobnetRunData.InnerJobnetId, common.StatusRunErr, -1, -1, queries...)
		queries = jaStatusJobnetDelayStart(jobnetRunData.InnerJobnetId, queries...)
		nextEventData := prepareNextEventData(
			common.EventJobnetDelayError,
			common.DBSyncerManagerProcess,
			map[string]any{},
			queries,
		)
		if err := event.CreateNextEvent(nextEventData, jobnetRunData.InnerJobnetId, jobnetRunData.JobnetID, jobnetRunData.InnerJobId); err != nil {
			JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
			return fmt.Errorf("%s", err.Error())
		}
		return nil
	}

	// Handle scheduled jobs asynchronously
	if jobnetRunData.RunType == JA_JOBNET_RUN_TYPE_SCHEDULED || jobnetRunData.RunType == JA_JOBNET_RUN_TYPE_NORMAL {
		runScheduledJobnetAsync(context.Background(), jobnetRunData, currentFilePath, nextEventData)
		return nil
	} else {
		JobnetRunLogger.InnerJobnetID = jobnetRunData.InnerJobnetId
		JobnetRunLogger.InnerJobnetMainID = jobnetRunData.InnerJobnetId
		JobnetRunLogger.JobnetID = jobnetRunData.JobnetID
		JobnetRunLogger.JobnetName = jobnetRunData.JobnetName
		JobnetRunLogger.JaJobLog(common.JC_JOBNET_START)
	}

	// Handle normal jobs synchronously
	err = MoveFileAndCreateEvent(jobnetRunData, currentFilePath, moveFilePath, nextEventData)
	if err != nil {
		JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
		return fmt.Errorf("%s", err.Error())
	}
	JobnetRunLogger.JaLog("JAJOBNETRUN000003", jobnetRunData.InnerJobnetId)

	JobnetRunLogger.JaLog("JAJOBNETRUN400002", funcname)
	return nil
}

func prepareQueriesAndNextEvent(jobnetRunData common.JobnetRunData, dbconn database.DBConnection) (common.EventData, string, error) {
	funcname := "prepareQueriesAndNextEvent"
	JobnetRunLogger.JaLog("JAJOBNETRUN400001", funcname)
	queries := []common.ParameterizedQuery{}
	var (
		nextEventData common.EventData
		moveFilePath  string
		err           error
	)

	processReady := func() error {
		JobnetRunLogger.JaLog("JAJOBNETRUN400007", jobnetRunData.InnerJobnetId)
		queries, err := jaJobnetReady(&jobnetRunData, dbconn, queries...)
		if err != nil {
			JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
			return fmt.Errorf("%s", err.Error())
		}
		moveFilePath = RUN

		if jobnetRunData.MainFlag == common.JA_JOBNET_MAIN_FLAG_MAIN {
			queries = jaStartTheJobnet(jobnetRunData.InnerJobnetId, queries...)
			if jobnetRunData.ScheduledTime == 0 {
				queries = jaJobnetSummaryReady(jobnetRunData.InnerJobnetId, queries...)
			}
		}
		queries = jaJobnetTimeout(jobnetRunData, queries...)

		nextEventData = prepareNextEventData(
			common.EventJobnetRun,
			common.FlowManagerProcess,
			common.FlowProcessData{
				InnerJobnetId: jobnetRunData.InnerJobnetId,
				JobnetId:      jobnetRunData.JobnetID,
				InnerJobId:    jobnetRunData.InnerJobId,
				IconType:      common.IconTypeStart,
				FlowType:      common.FlowNormal,
			},
			queries,
		)
		return nil
	}

	if jobnetRunData.MultipleOptions != JA_JOBNET_MULTIPLE_YES {
		nextEventData, moveFilePath, err = handleSkipAndWait(jobnetRunData.MultipleOptions, jobnetRunData.InnerJobnetId, jobnetRunData.JobnetID)
		if err != nil {
			JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
			return common.EventData{}, "", fmt.Errorf("%s", err.Error())
		}
		// If SKIP or WAIT was applied
		if moveFilePath != RUN {
			return nextEventData, moveFilePath, nil
		}
	}

	//handle Multiple Option Yes
	if err := processReady(); err != nil {
		return common.EventData{}, "", fmt.Errorf("%s", err.Error())
	}

	return nextEventData, moveFilePath, nil
}

// handleJobnetProcessing contains the core logic for jobnet processing
func MoveFileAndCreateEvent(jobnetRunData common.JobnetRunData, currentFilePath, moveFilePath string, nextEventData common.EventData) error {
	funcname := "MoveFileAndCreateEvent"
	JobnetRunLogger.JaLog("JAJOBNETRUN400001", funcname)
	// Move file to destination folder if needed
	JobnetRunLogger.JaLog("JAJOBNETRUN400009", nextEventData)
	if err := event.CreateNextEvent(nextEventData, jobnetRunData.InnerJobnetId, jobnetRunData.JobnetID, jobnetRunData.InnerJobId); err != nil {
		return fmt.Errorf("%v", err)
	}

	if moveFilePath != "" {
		JobnetRunLogger.JaLog("JAJOBNETRUN400005", currentFilePath, moveFilePath)
		err := utils.MoveToSubFolder(currentFilePath, moveFilePath)
		if err != nil {
			return fmt.Errorf("moveFile from current %s to %s failed : %s", currentFilePath, moveFilePath, err.Error())
		}
	}
	return nil
}

func handleSkipAndWait(multipleOption int, innerJobnetId uint64, jobnetId string) (common.EventData, string, error) {
	funcname := "prepareQueriesAndNextEvent"
	JobnetRunLogger.JaLog("JAJOBNETRUN400001", funcname)
	var (
		moveFilePath  = RUN
		queries       []common.ParameterizedQuery
		nextEventData common.EventData
	)
	file, err := getSortedRunningJobnetFile(jobnetId, JOBNETMANAGER_RUN_DIR)
	if err != nil {
		JobnetRunLogger.JaLog("JAJOBNETRUN400003", funcname, err.Error())
		return common.EventData{}, "", fmt.Errorf("%s", err.Error())
	}

	if file != "" {
		switch multipleOption {
		case JA_JOBNET_MULTIPLE_SKIP:
			queries = jaSetStatusJobnetSkip(innerJobnetId, queries...)
			moveFilePath = END
		case JA_JOBNET_MULTIPLE_WAIT:
			queries = jaSetStatusJobnetWait(innerJobnetId, queries...)
			moveFilePath = WAIT
		}
		nextEventData = prepareNextEventData(
			common.EventJobnetStatusChange,
			common.DBSyncerManagerProcess,
			map[string]any{},
			queries,
		)
	}
	return nextEventData, moveFilePath, nil
}
