/*
** 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"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"strings"

	"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"
)

func handleDBError(err error, dbConn database.DBConnection, eventData *common.EventData, logData logger.Logging, filePath, query string) error {
	fn := "handleDBError"
	filename := filepath.Base(filePath)

	errCode := strings.TrimSpace(dbConn.DBErrCode())
	errMsg := dbConn.DBErrMessage()

	eventData.DBSyncResult.Success = false
	eventData.DBSyncResult.Error = err.Error()

	rbErr := dbConn.Rollback()
	if rbErr != nil {
		logger.JaLog("JADBSYNCER200005", logData, fn, filename, rbErr.Error())
	}

	// For postgresql; where error code is empty
	if rsp, ok := dbConn.(database.ResultStatusProvider); ok {
		status := rsp.DBResultStatus()

		// 7 - PGRES_FATAL_ERROR
		if status == 7 && errCode == "" {
			// Retry
			logger.JaLog("JADBSYNCER200016", logData, fn, filename, eventData.Event.Name)
			logger.JaLog("JADBERROR200002", logData, errCode, errMsg, query, string(debug.Stack()))
			os.Exit(1)
		}
	}

	errorType := classifySQLError(errCode)
	eventData.DBSyncResult.ErrorType = string(errorType)

	switch errorType {
	case DBNonRetryableError:
		logger.JaLog("JADBSYNCER200015", logData, fn, filename, eventData.Event.Name)
		// not retryable error, create run_err transaction file to respective manager
		if err := handleNonRetryableDBError(*eventData, logData); err != nil {
			logger.JaLog("JADBSYNCER200014", logData, fn, filename, err.Error())
		}

		return &NonRetryableError{
			ErrorMsg: fmt.Sprintf("non-retryable error occurred"),
		}
	case DBRetryableError:
		logger.JaLog("JADBERROR200002", logData, errCode, errMsg, query, string(debug.Stack()))
		os.Exit(1)
	case DBSyncRetryableError:
		return recreateEvent(eventData, filePath)
	}

	return nil

}

func handleNonRetryableDBError(eventData common.EventData, logData logger.Logging) error {
	fn := "handleNonRetryableDBError"
	currentFilePath := eventData.Transfer.Files[0].Source
	currentFileName := filepath.Base(currentFilePath)

	logger.JaLog("JADBSYNCER400016", logData, fn, currentFileName, eventData.Event.Name)

	// Identify the type:
	switch eventData.Event.Name {
	case common.EventJobnetLoad:
	case common.EventJobnetRun:
		// Create event file for jobnetmanager
		eventData.Event.Name = common.EventDBJobnetRunQueryFailure
		eventData.Event.UniqueKey = common.GetUniqueKey(common.DBSyncerManagerProcess)
		eventData.NextProcess.Name = common.JobnetManagerProcess
		eventData.Transfer.Files = []common.FileTransfer{}
		flowProcessData, ok := eventData.NextProcess.Data.(common.FlowProcessData)
		if !ok {
			return fmt.Errorf("failed to type cast to 'common.FlowProcessData': data %v", eventData.NextProcess.Data)
		}

		// Change to JobnetRunData
		eventData.NextProcess.Data = common.JobnetRunData{
			InnerJobnetId: flowProcessData.InnerJobnetId,
			InnerJobId:    flowProcessData.InnerJobId,
			JobnetID:      flowProcessData.JobnetId,
			Message:       eventData.DBSyncResult.Error,
		}

		// Create event
		if err := event.CreateNextEvent(eventData, logData.InnerJobnetID, logData.JobnetID, logData.InnerJobID); err != nil {
			return fmt.Errorf("failed to create runerror event for jobnet manager: %v", err)
		}

		logger.JaLog("JADBSYNCER400018", logData, fn, currentFileName, common.JobnetManagerProcess)

	// notification and run log case
	case common.EventInsertLogTable, common.EventInsertSendMsgTable:
	case common.EventIconExecRun:
		// Send to result manager
		// Create a new transaction file for iconresultmanager
		logger.JaLog("JADBSYNCER400017", logData, fn, currentFileName, logData.InnerJobnetID, logData.JobnetID, logData.InnerJobID)

		iconRunData, ok := eventData.NextProcess.Data.(common.IconRunData)
		if !ok {
			return fmt.Errorf("failed to type cast to 'common.IconRunData': data %v", eventData.NextProcess.Data)
		}

		return sendToResultManager(eventData, logData, currentFileName, iconRunData.ExecProcessData)

	// icon execution failure: flowmanager, exec manager => DBSyncer
	case common.EventIconReady, common.EventIconExecEnd, common.EventIconAbort:
		// Send to result manager
		// Create a new transaction file for iconresultmanager
		logger.JaLog("JADBSYNCER400017", logData, fn, currentFileName, logData.InnerJobnetID, logData.JobnetID, logData.InnerJobID)

		iconExecProcessData, ok := eventData.NextProcess.Data.(common.IconExecutionProcessData)
		if !ok {
			return fmt.Errorf("failed to type cast to 'common.IconExecutionProcessData': data %v", eventData.NextProcess.Data)
		}

		return sendToResultManager(eventData, logData, currentFileName, iconExecProcessData)

	// icon execution failure: result manager => DBSyncer
	case common.EventIconResultEnd, common.EventIconResultRunErr, common.EventIconResultEndErr, common.EventIconTimeoutStop:
		// Send to result manager
		// Create a new transaction file for iconresultmanager
		logger.JaLog("JADBSYNCER400017", logData, fn, currentFileName, logData.InnerJobnetID, logData.JobnetID, logData.InnerJobID)

		flowProcessData, ok := eventData.NextProcess.Data.(common.FlowProcessData)
		if !ok {
			return fmt.Errorf("failed to type cast to 'common.IconExecutionProcessData': data %v", eventData.NextProcess.Data)
		}

		var iconExecProcessData common.IconExecutionProcessData
		if err := utils.Convert(flowProcessData.Data, &iconExecProcessData); err != nil {
			return fmt.Errorf("failed to convert to 'common.IconExecutionProcessData': data %v", flowProcessData.Data)
		}

		return sendToResultManager(eventData, logData, currentFileName, iconExecProcessData)

	case common.EventIconStop, common.EventDelayedStart, common.EventScheduleUpdate,
		common.EventScheduleDelete:
		// User interrupts
	default:
		logger.JaLog("JADBSYNCER300001", logData, fn, currentFileName, eventData.Event.Name)
	}

	return nil

}

func sendToResultManager(eventData common.EventData, logData logger.Logging, currentFileName string, iconExecProcessData common.IconExecutionProcessData) error {
	fn := "sendToResultManager"

	// Preparation of event data
	eventData.Event.Name = common.EventIconResultUpdate
	eventData.Event.UniqueKey = common.GetUniqueKey(common.DBSyncerManagerProcess)
	eventData.NextProcess.Name = common.IconResultManagerProcess
	eventData.Transfer.Files = []common.FileTransfer{}

	iconExecProcessData.JobResult.Message = eventData.DBSyncResult.Error
	iconExecProcessData.JobResult.JobID = strconv.FormatUint(iconExecProcessData.RunJobData.InnerJobID, 10)
	iconType := iconExecProcessData.RunJobData.IconType
	if iconType == common.IconTypeJob || iconType == common.IconTypeFWait || iconType == common.IconTypeReboot || iconType == common.IconTypeFCopy {
		iconExecProcessData.JobResult.Result = common.JA_RESPONSE_FAIL
	} else {
		iconExecProcessData.JobResult.Result = common.JA_JOBRESULT_FAIL
	}

	eventData.NextProcess.Data = iconExecProcessData

	// Create event
	if err := event.CreateNextEvent(eventData, logData.InnerJobnetID, logData.JobnetID, logData.InnerJobID); err != nil {
		return fmt.Errorf("failed to create runerror event for result manager: %v", err)
	}

	logger.JaLog("JADBSYNCER400018", logData, fn, currentFileName, common.IconResultManagerProcess)

	return nil
}
