/*
** 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 utils

import (
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"

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

// FetchSingalRow retrieves a single row from the specified table and maps it into the provided struct pointer.
//
// It constructs a SELECT query using optional WHERE conditions passed as variadic strings.
// The results are fetched from the database and converted into the provided output struct using
// common.MapStringStringToStruct.
//
// Parameters:
//   - connection: The database connection interface used to execute the query.
//   - tableName:  The name of the table to query.
//   - out:        A pointer to a struct where the result will be unmarshaled.
//   - conditions: Optional strings to be used as SQL WHERE conditions (e.g., "id = '123'").
//
// Returns:
//   - error: An error if query execution or result mapping fails.
//
// Note:
//   - The 'out' parameter must be a pointer to a struct.
//   - If no row is returned, 'out' remains unchanged, and the error is nil.
//
// Usage:
//
//	var runJobData RunJobData
//	queryExt := fmt.Sprintf("inner_job_id = '%d'", innerJobId)
//	err := FetchSingalRow(conn, common.Ja2RunJobTable, &runJobData, queryExt)
//	if err != nil {
//	    error handling
//	}
func FetchSingleRow(
	connection database.DBConnection,
	tableName common.TableName,
	out any,
	conditions ...string,
) error {

	if reflect.ValueOf(out).Kind() != reflect.Ptr || reflect.ValueOf(out).Elem().Kind() != reflect.Struct {
		return fmt.Errorf("out must be a pointer to a struct, got %T", out)
	}

	query := fmt.Sprintf("SELECT * FROM %s ", tableName)
	if len(conditions) > 0 {
		query += " WHERE " + strings.Join(conditions, " AND ")
	}
	query += " LIMIT 1"

	result, err := connection.Select(query)
	defer result.Free()
	if err != nil {
		if connection.IsClosed() {
			os.Exit(1)
		}
		return fmt.Errorf("query execution failed. query: '%s', err: [%v]", query, err)
	}
	if result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			// THIS SHOULD NEVER HAPPENS
			return fmt.Errorf("fetching result failed. THIS SHOULD NEVER HAPPENS. err: [%v]", err)
		}
		if err = MapStringStringToStruct(row, out); err != nil {
			return err
		}
	}
	return nil
}

func FetchColumnsOfSingleRow(
	connection database.DBConnection,
	tableName common.TableName,
	out any,
	columns []string,
	conditions []string,
) error {
	if reflect.ValueOf(out).Kind() != reflect.Ptr || reflect.ValueOf(out).Elem().Kind() != reflect.Struct {
		return fmt.Errorf("out must be a pointer to a struct, got %T", out)
	}
	if len(columns) == 0 {
		return fmt.Errorf("at least one column must be specified")
	}

	query := fmt.Sprintf("SELECT %s FROM %s", strings.Join(columns, ", "), tableName)
	if len(conditions) > 0 {
		query += " WHERE " + strings.Join(conditions, " AND ")
	}
	query += " LIMIT 1"

	result, err := connection.Select(query)
	defer result.Free()
	if err != nil {
		if connection.IsClosed() {
			os.Exit(1)
		}
		return fmt.Errorf("query execution failed. query: '%s', err: [%v]", query, err)
	}
	if result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			// THIS SHOULD NEVER HAPPENS
			return fmt.Errorf("fetching result failed. err: [%v]", err)
		}
		if err = MapStringStringToStruct(row, out); err != nil {
			return err
		}
	}
	return nil
}

// FetchMultipleRow retrieves multiple rows from the specified table and populates the provided slice pointer.
//
// It constructs a SELECT query using optional WHERE conditions passed as variadic strings.
// Each resulting row is mapped into a struct using common.MapStringStringToStruct, and appended to the output slice.
//
// Parameters:
//   - connection: The database connection interface used to execute the query.
//   - tableName:  The name of the table to query.
//   - outSlice:   A pointer to a slice of structs where results will be stored.
//   - conditions: Optional strings to be used as SQL WHERE conditions (e.g., "status = 'active'").
//
// Returns:
//   - error: An error if query execution or result mapping fails.
//
// Notes:
//   - The 'outSlice' parameter must be a pointer to a slice (e.g., *[]YourStruct).
//   - Each row in the result will be unmarshaled into a new struct and added to the slice.
//
// Usage:
//
//	var runJobDataList []RunJobTable
//	queryExt += fmt.Sprintf("status = '%d'", value)
//	if err := FetchMultipleRow(connection, Ja2RunJobTable, &runJobDataList, queryExt); err != nil {
//		return nil, err
//	}
func FetchMultipleRow(
	connection database.DBConnection,
	tableName common.TableName,
	outSlice interface{},
	conditions ...string,
) error {
	outValue := reflect.ValueOf(outSlice)
	if outValue.Kind() != reflect.Ptr ||
		outValue.Elem().Kind() != reflect.Slice ||
		outValue.Elem().Type().Elem().Kind() != reflect.Struct {
		return fmt.Errorf("out must be a pointer to a slice of struct, got %T", outSlice)
	}

	sliceValue := outValue.Elem()
	elemType := sliceValue.Type().Elem()

	query := fmt.Sprintf("SELECT * FROM %s", tableName)
	if len(conditions) > 0 {
		query += " WHERE " + strings.Join(conditions, " AND ")
	}

	result, err := connection.Select(query)
	defer result.Free()
	if err != nil {
		if connection.IsClosed() {
			os.Exit(1)
		}
		return fmt.Errorf("query execution failed. query: '%s', err: [%v]", query, err)
	}

	for result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			// THIS SHOULD NEVER HAPPENS
			return fmt.Errorf("fetching result failed. THIS SHOULD NEVER HAPPENS. err: [%v]", err)
		}

		// ## declare var out with the received interface
		// Step 1: Create a new instance of the element type (as pointer)
		newElemPtr := reflect.New(elemType) // *T

		// Step 2: Fill the struct from the map
		if err = MapStringStringToStruct(row, newElemPtr.Interface()); err != nil {
			return err
		}

		// Step 3: Dereference the pointer (to get the struct value)
		newElem := newElemPtr.Elem()

		// Step 4: Append the struct to the slice
		sliceValue = reflect.Append(sliceValue, newElem)
	}

	outValue.Elem().Set(sliceValue)
	return nil
}

func FetchColumnsOfMultipleRow(
	connection database.DBConnection,
	tableName common.TableName,
	outSlice interface{},
	columns []string,
	conditions []string,
) error {
	outValue := reflect.ValueOf(outSlice)
	if outValue.Kind() != reflect.Ptr ||
		outValue.Elem().Kind() != reflect.Slice ||
		outValue.Elem().Type().Elem().Kind() != reflect.Struct {
		return fmt.Errorf("out must be a pointer to a slice of struct, got %T", outSlice)
	}

	sliceValue := outValue.Elem()
	elemType := sliceValue.Type().Elem()

	query := fmt.Sprintf("SELECT %s FROM %s", strings.Join(columns, ", "), tableName)
	if len(conditions) > 0 {
		query += " WHERE " + strings.Join(conditions, " AND ")
	}

	result, err := connection.Select(query)
	defer result.Free()
	if err != nil {
		if connection.IsClosed() {
			os.Exit(1)
		}
		return fmt.Errorf("query execution failed. query: '%s', err: [%v]", query, err)
	}

	for result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			// THIS SHOULD NEVER HAPPENS
			return fmt.Errorf("fetching result failed. THIS SHOULD NEVER HAPPENS. err: [%v]", err)
		}

		// ## declare var out with the received interface
		// Step 1: Create a new instance of the element type (as pointer)
		newElemPtr := reflect.New(elemType) // *T

		// Step 2: Fill the struct from the map
		if err = MapStringStringToStruct(row, newElemPtr.Interface()); err != nil {
			return err
		}

		// Step 3: Dereference the pointer (to get the struct value)
		newElem := newElemPtr.Elem()

		// Step 4: Append the struct to the slice
		sliceValue = reflect.Append(sliceValue, newElem)
	}

	outValue.Elem().Set(sliceValue)
	return nil
}

// start of ja_2_run_job_table data fetching *****************************************************************
func GetRunJobData(connection database.DBConnection, innerJobId uint64) (common.RunJobTable, error) {
	var runJobData common.RunJobTable
	queryExt := fmt.Sprintf("inner_job_id = '%d'", innerJobId)
	if err := FetchSingleRow(connection, common.Ja2RunJobTable, &runJobData, queryExt); err != nil {
		return runJobData, err
	}
	return runJobData, nil
}

func GetRunningJobIDList(connection database.DBConnection, innerJobnetId uint64) ([]uint64, error) {
	var runJobDataList []common.RunJobTable
	var jobIDList []uint64
	var columns, conditions []string
	columns = append(columns, "inner_job_id")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = %d", innerJobnetId))
	conditions = append(conditions, fmt.Sprintf("status IN (%d,%d,%d)", common.StatusRun, common.StatusReady, common.StatusAbort))
	conditions = append(conditions, "method_flag <> 1")
	err := FetchColumnsOfMultipleRow(connection, common.Ja2RunJobTable, &runJobDataList, columns, conditions)
	if err != nil {
		return nil, fmt.Errorf("FetchColumnsOfMultipleRow() failed, err: [%v]", err)
	}
	for _, runJobData := range runJobDataList {
		jobIDList = append(jobIDList, runJobData.InnerJobID)
	}
	return jobIDList, nil
}

func GetPrevIconCountByStatus(
	connection database.DBConnection,
	innerJobId uint64,
	ignoreContinueFlag bool,
	statuses ...common.StatusType,
) (int, error) {
	var result struct {
		Count int `json:"count"`
	}
	var columns, conditions []string

	startInnerJobIdList, err := GetPrevFlowJobIDList(connection, innerJobId)
	if err != nil {
		return -1, fmt.Errorf("GetPrevFlowJobIDList(%d) failed, err: [%v]", innerJobId, err)
	}
	ids := make([]string, len(startInnerJobIdList))
	if len(startInnerJobIdList) > 0 {
		for i, id := range startInnerJobIdList {
			ids[i] = strconv.FormatUint(id, 10)
		}
	} else {
		return 0, fmt.Errorf("count not find previous icon from run flow table for inner_job_id: %d", innerJobId)
	}

	columns = append(columns, "count(*) as count")
	conditions = append(conditions, fmt.Sprintf("inner_job_id in(%s)", strings.Join(ids, ",")))

	if len(statuses) > 0 {
		statusStrings := make([]string, len(statuses))
		for i, s := range statuses {
			statusStrings[i] = fmt.Sprintf("%d", s)
		}
		condStr := ""
		if ignoreContinueFlag {
			condStr = fmt.Sprintf(" or (status = %d and continue_flag != %d)", common.StatusRunErr, common.FlagOn)
			statusStrings = RemoveFirstString(statusStrings, fmt.Sprintf("%d", common.StatusRunErr))
		}
		conditions = append(conditions, fmt.Sprintf("(status IN (%s)%s)", strings.Join(statusStrings, ","), condStr))
	}

	err = FetchColumnsOfSingleRow(connection, common.Ja2RunJobTable, &result, columns, conditions)
	if err != nil {
		return -1, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return result.Count, nil
}

func GetCountByJobIdAndStatus(connection database.DBConnection, innerJobnetId uint64, innerJobId uint64, statuses ...common.StatusType) (int, error) {
	var result struct {
		Count int `json:"count"`
	}
	var columns, conditions []string
	columns = append(columns, "count(*) as count")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = %d", innerJobnetId))

	if len(statuses) > 0 {
		statusStrings := make([]string, len(statuses))
		for i, s := range statuses {
			statusStrings[i] = fmt.Sprintf("%d", s)
		}
		conditions = append(conditions, fmt.Sprintf("( status IN (%s) OR method_flag = 3 )", strings.Join(statusStrings, ",")))
		conditions = append(conditions, fmt.Sprintf("inner_job_id <> %d", innerJobId))
		conditions = append(conditions, fmt.Sprintf("(continue_flag <> 1 OR method_flag = 3)"))
	}

	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &result, columns, conditions); err != nil {
		return -1, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return result.Count, nil
}

func GetIconContinueFlag(connection database.DBConnection, innerJobId uint64) (int, error) {
	var runJobData common.RunJobTable
	var columns, conditions []string
	columns = append(columns, "continue_flag")
	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", innerJobId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return -1, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return runJobData.ContinueFlag, nil
}

func GetIconData(connection database.DBConnection, innerJobId uint64) (map[string]interface{}, error) {
	var runJobData common.RunJobTable
	var columns, conditions []string
	columns = append(columns, "data")
	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", innerJobId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return nil, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return runJobData.Data, nil
}

func GetRunJobIdByTypeAndInnerJobnetId(
	connection database.DBConnection,
	iconType common.IconType,
	innerJobnetId uint64,
) (uint64, error) {

	var runJobData common.RunJobTable
	var columns, conditions []string

	columns = append(columns, "inner_job_id")
	conditions = append(conditions, fmt.Sprintf("job_type = %d", iconType))
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = %d", innerJobnetId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}
	return runJobData.InnerJobID, nil
}

func GetInnerJobIdFromRunJobTableByJobIdAndInnerJobnetId(
	connection database.DBConnection,
	jobId string, innerJobnetId uint64,
) (uint64, error) {

	var runJobData common.RunJobTable
	var columns, conditions []string

	columns = append(columns, "inner_job_id")
	conditions = append(conditions, fmt.Sprintf("job_id = '%s'", jobId))
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = %d", innerJobnetId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}
	return runJobData.InnerJobID, nil
}

func GetStatusFromRunJobTableByInnerJobId(
	connection database.DBConnection,
	innerJobId uint64,
) (common.StatusType, error) {

	var runJobData common.RunJobTable
	var columns, conditions []string

	columns = append(columns, "status")
	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", innerJobId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}
	return runJobData.Status, nil
}

func GetMethodFlagFromRunJobTableByInnerJobId(
	connection database.DBConnection,
	innerJobId uint64,
) (common.JobRunMethod, error) {

	var runJobData common.RunJobTable
	var columns, conditions []string

	columns = append(columns, "method_flag")
	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", innerJobId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}
	return runJobData.MethodFlag, nil
}

func GetStatusFromRunJobTableByJobIdAndInnerJobnetId(
	connection database.DBConnection,
	jobId string, innerJobnetId uint64,
) (common.StatusType, error) {

	var runJobData common.RunJobTable
	var columns, conditions []string

	columns = append(columns, "status", "job_id")
	conditions = append(conditions, fmt.Sprintf("job_id = '%s'", jobId))
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = %d", innerJobnetId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}

	if runJobData.JobID == "" {
		runJobData.Status = -1
	}

	return runJobData.Status, nil
}

func GetInnerMostSubJobnetIdByPhrase(
	connection database.DBConnection,
	parts []string,
	innerJobnetId uint64,
) (uint64, error) {

	var innerJobId uint64
	var err error
	jobId := ""

	subJobnetCount := 0
	// fetch the innermost sub inner_jobnet_id
	for {
		jobId = parts[subJobnetCount]
		innerJobId, err = GetInnerJobIdFromRunJobTableByJobIdAndInnerJobnetId(
			connection, jobId, innerJobnetId)
		if err != nil {
			return 0, fmt.Errorf("GetInnerJobIdFromJobId() failed, err: [%v]", err)
		}
		if innerJobId == 0 {
			innerJobnetId, err = GetInnerJobnetIdFromRunJobnetSummaryTable(connection, jobId)
			if err != nil {
				return 0, fmt.Errorf("could not get related sub jobnet info for job_id: %s", jobId)
			}
		} else {
			innerJobnetId, err = GetInnerJobnetIDFromRunJobnetTableByInnerJobId(connection, innerJobId)
			if err != nil {
				return 0, fmt.Errorf("GetInnerJobnetIDFromRunJobTableByInnerJobId() failed, err: [%v]", err)
			}
		}

		subJobnetCount++
		if subJobnetCount == len(parts)-1 {
			// leave the last part as icon
			break
		}
	}
	return innerJobnetId, nil
}

func GetInnerJobIdFromRunJobTableByPhrase(
	connection database.DBConnection,
	phrase string,
	innerJobnetId uint64,
) (uint64, error) {

	var jobId string
	var innerJobId uint64
	var err error

	// phrase example ["jobnet_id/jobnet_id/job_id"]
	parts := strings.Split(phrase, "/")
	if len(parts) > 1 {
		innerJobnetId, err = GetInnerMostSubJobnetIdByPhrase(connection, parts, innerJobnetId)
		if err != nil {
			return 0, fmt.Errorf("GetInnerMostSubJobnetIdByPhrase() failed, err: [%v]", err)
		}
	}

	//fetch the icon's inner_job_id
	jobId = parts[len(parts)-1]
	innerJobId, err = GetInnerJobIdFromRunJobTableByJobIdAndInnerJobnetId(
		connection, jobId, innerJobnetId)
	if err != nil {
		return 0, fmt.Errorf("GetInnerJobIdFromRunJobTableByJobIdAndInnerJobnetId() failed, err: [%v]", err)
	}

	return innerJobId, nil
}

func GetFullJobId(
	connection database.DBConnection,
	innerJobnetMainId, innerJobnetId uint64,
	jobId string,
) (string, error) {
	var jobnetId string
	var innerJobId uint64
	var err error

	for {
		innerJobId, jobnetId, err = GetInnerJobIdAndJobnetIDFromRunJobnetTableByInnerJobnetId(connection, innerJobnetId)
		if err != nil {
			return "", fmt.Errorf("GetInnerJobIdAndJobnetIDFromRunJobnetTableByInnerJobnetId() failed, err: [%v]", err)
		}

		// add jobnetId to fullpath, eg: jobnet-1/jobnet-2/job-1
		jobId = fmt.Sprintf("%s/%s", jobnetId, jobId)

		// this is parent jobnet and no need to loop anymore
		if innerJobId == 0 {
			break
		}

		innerJobnetId, err = GetInnerJobnetIDFromRunJobTableByInnerJobId(connection, innerJobId)
		if err != nil {
			return "", fmt.Errorf("GetInnerJobnetIDFromRunJobTableByInnerJobId() failed, err: [%v]", err)
		}
	}

	return jobId, err
}

func GetStatusFromRunJobTableByPhrase(
	connection database.DBConnection,
	phrase string,
	innerJobnetId uint64,
) (common.StatusType, error) {

	var jobId string
	var status common.StatusType
	var err error

	// phrase example ["jobnet_id/jobnet_id/job_id"]
	parts := strings.Split(phrase, "/")
	if len(parts) > 1 {
		innerJobnetId, err = GetInnerMostSubJobnetIdByPhrase(connection, parts, innerJobnetId)
		if err != nil {
			return 0, fmt.Errorf("GetInnerMostSubJobnetIdByPhrase() failed, err: [%v]", err)
		}
	}

	//fetch the icon's inner_job_id
	jobId = parts[len(parts)-1]
	status, err = GetStatusFromRunJobTableByJobIdAndInnerJobnetId(
		connection, jobId, innerJobnetId)
	if err != nil {
		return 0, fmt.Errorf("GetStatusFromRunJobTableByJobIdAndInnerJobnetId() failed, err: [%v]", err)
	}

	return status, nil
}

// end of ja_2_run_job_table data fetching *****************************************************************

// start of ja_2_run_job_variable_table data fetching *****************************************************************
func GetLastRunJobVariableData(connection database.DBConnection, innerJobId uint64) (common.RunJobVariableTable, error) {
	var runJobVariabledata common.RunJobVariableTable

	queryExt := fmt.Sprintf("inner_job_id = '%d' order by seq_no desc", innerJobId)
	if err := FetchSingleRow(connection, common.Ja2RunJobVariableTable, &runJobVariabledata, queryExt); err != nil {
		return runJobVariabledata, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobVariabledata, nil
}

// end of ja_2_run_job_variable_table data fetching *****************************************************************

// start of ja_2_run_value_jobcon_table data fetching *****************************************************************
func GetRunValueJobConData(connection database.DBConnection, innerJobId uint64) ([]common.RunValueJobConTable, error) {
	var runJobConDataList []common.RunValueJobConTable

	queryExt := fmt.Sprintf("inner_job_id = '%d'", innerJobId)
	if err := FetchMultipleRow(connection, common.Ja2RunValueJobconTable, &runJobConDataList, queryExt); err != nil {
		return runJobConDataList, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}
	return runJobConDataList, nil
}

// end of ja_2_run_value_jobcon_table data fetching *****************************************************************

// start of ja_2_run_value_job_table data fetching *****************************************************************
func GetRunValueJobData(connection database.DBConnection, innerJobId uint64) ([]common.RunValueJobTable, error) {
	var runValueJobDataList []common.RunValueJobTable

	queryExt := fmt.Sprintf("inner_job_id = '%d'", innerJobId)
	if err := FetchMultipleRow(connection, common.Ja2RunValueJobTable, &runValueJobDataList, queryExt); err != nil {
		return runValueJobDataList, fmt.Errorf("FetchMultipleRow() failed, err: [%v]", err)
	}
	return runValueJobDataList, nil
}

// end of ja_2_run_value_job_table data fetching *****************************************************************

// start of ja_2_run_value_job_table data fetching *****************************************************************
func GetSessionData(connection database.DBConnection, sessionId string, innerJobnetMainId uint64) (common.SessionTable, error) {
	var sessionData common.SessionTable

	queryExt := fmt.Sprintf("session_id = '%s' and inner_jobnet_main_id = '%d'", sessionId, innerJobnetMainId)
	if err := FetchSingleRow(connection, common.Ja2SessionTable, &sessionData, queryExt); err != nil {
		return sessionData, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return sessionData, nil
}

// end of ja_2_run_value_job_table data fetching *****************************************************************

// start of ja_2_run_jobnet_summary_table data fetching *****************************************************************
func GetRunJobnetSummaryTableData(connection database.DBConnection, innerJobnetId uint64) (common.RunJobnetSummaryTable, error) {
	var runJobnetSummaryTable common.RunJobnetSummaryTable
	queryExt := fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId)
	if err := FetchSingleRow(connection, common.Ja2RunJobnetSummaryTable, &runJobnetSummaryTable, queryExt); err != nil {
		return runJobnetSummaryTable, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetSummaryTable, nil
}

// end of ja_2_run_jobnet_summary_table data fetching *****************************************************************

// start of ja_2_run_jobnet_table data fetching *****************************************************************
func GetRunJobnetTableDataByInnerJobnetId(connection database.DBConnection, innerJobnetId uint64) (common.RunJobnetTable, error) {
	var runJobnetTable common.RunJobnetTable
	queryExt := fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId)
	if err := FetchSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, queryExt); err != nil {
		return runJobnetTable, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable, nil
}

func GetInnerJobIdFromRunJobnetTableByInnerJobnetId(connection database.DBConnection, innerJobnetId uint64) (uint64, error) {
	var runJobnetTable common.RunJobnetTable
	var columns, conditions []string

	columns = append(columns, "inner_job_id")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId))
	if err := FetchColumnsOfSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable.InnerJobID, nil
}

func GetRunJobnetTableDataByInnerJobId(connection database.DBConnection, innerJobId uint64) (common.RunJobnetTable, error) {
	var runJobnetTable common.RunJobnetTable
	queryExt := fmt.Sprintf("inner_job_id = '%d'", innerJobId)
	if err := FetchSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, queryExt); err != nil {
		return runJobnetTable, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable, nil
}

func GetStatusFromRunJobnetTable(connection database.DBConnection, innerJobnetId uint64) (common.StatusType, uint64, error) {
	var runJobnetTable common.RunJobnetTable
	var columns, conditions []string

	columns = append(columns, "status")
	columns = append(columns, "inner_jobnet_main_id")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId))
	if err := FetchColumnsOfSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, columns, conditions); err != nil {
		return -1, 0, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable.Status, runJobnetTable.InnerJobnetMainID, nil
}

func GetInnerJobnetMainIdFromRunJobnetTable(connection database.DBConnection, innerJobnetId uint64) (uint64, error) {
	var runJobnetTable common.RunJobnetTable
	var columns, conditions []string

	columns = append(columns, "inner_jobnet_main_id")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId))
	if err := FetchColumnsOfSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable.InnerJobnetMainID, nil
}

func GetInnerJobnetIdFromRunJobnetSummaryTable(connection database.DBConnection, jobnetId string) (uint64, error) {
	var runJobnetTable common.RunJobnetTable
	var columns, conditions []string

	columns = append(columns, "inner_jobnet_id")
	conditions = append(conditions, fmt.Sprintf("jobnet_id = '%s'", jobnetId))
	conditions = append(conditions, fmt.Sprintf("status = '%d'", common.StatusRun))
	if err := FetchColumnsOfSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable.InnerJobnetID, nil
}

func GetInnerJobIdAndJobnetIDFromRunJobnetTableByInnerJobnetId(connection database.DBConnection, innerJobnetId uint64) (uint64, string, error) {
	var runJobnetTable common.RunJobnetTable
	var columns, conditions []string

	columns = append(columns, "inner_job_id", "jobnet_id")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId))
	if err := FetchColumnsOfSingleRow(connection, common.Ja2RunJobnetTable, &runJobnetTable, columns, conditions); err != nil {
		return 0, "", fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	return runJobnetTable.InnerJobID, runJobnetTable.JobnetID, nil
}

func GetInnerJobnetIDFromRunJobTableByInnerJobId(connection database.DBConnection, innerJobId uint64) (uint64, error) {
	var runJobData common.RunJobTable
	var columns, conditions []string
	columns = append(columns, "inner_jobnet_id")
	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", innerJobId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobTable, &runJobData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return runJobData.InnerJobnetID, nil
}

func GetInnerJobnetIDFromRunJobnetTableByInnerJobId(connection database.DBConnection, innerJobId uint64) (uint64, error) {
	var runJobnetData common.RunJobnetTable
	var columns, conditions []string
	columns = append(columns, "inner_jobnet_id")
	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", innerJobId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobnetTable, &runJobnetData, columns, conditions); err != nil {
		return 0, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return runJobnetData.InnerJobnetID, nil
}

func GetTimeoutFlag(connection database.DBConnection, innerJobnetId uint64) (int, error) {
	var runJobnetData common.RunJobnetTable
	var columns, conditions []string
	columns = append(columns, "timeout_flag")
	conditions = append(conditions, fmt.Sprintf("inner_jobnet_id = %d", innerJobnetId))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2RunJobnetTable, &runJobnetData, columns, conditions); err != nil {
		return -1, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return runJobnetData.TimeoutFlag, nil
}

// end of ja_2_run_jobnet_table data fetching *****************************************************************

// start of Ja2RunFlowTable data fetching *****************************************************************
func GetNextFlowJobIDList(connection database.DBConnection, innerJobId uint64, flowType common.FlowType) ([]uint64, error) {
	var runFlowDataList []common.RunFlowTable
	var nextJobIDList []uint64
	var columns, conditions []string

	columns = append(columns, "end_inner_job_id")
	conditions = append(conditions, fmt.Sprintf("start_inner_job_id = %d", innerJobId))
	if flowType != common.FlowNormal {
		conditions = append(conditions, fmt.Sprintf("flow_type = %d", flowType))
	}

	err := FetchColumnsOfMultipleRow(connection, common.Ja2RunFlowTable, &runFlowDataList, columns, conditions)
	if err != nil {
		return nil, fmt.Errorf("FetchColumnsOfMultipleRow() failed, err: [%v]", err)
	}
	for _, runFlowData := range runFlowDataList {
		nextJobIDList = append(nextJobIDList, runFlowData.EndInnerJobID)
	}
	return nextJobIDList, nil
}

func GetPrevFlowJobIDList(connection database.DBConnection, innerJobId uint64) ([]uint64, error) {
	var runFlowDataList []common.RunFlowTable
	var prevJobIDList []uint64
	var columns, conditions []string

	columns = append(columns, "start_inner_job_id")
	conditions = append(conditions, fmt.Sprintf("end_inner_job_id = %d", innerJobId))
	// if flowType != common.FlowNormal {
	// 	conditions = append(conditions, fmt.Sprintf("flow_type = %d", flowType))
	// }

	err := FetchColumnsOfMultipleRow(connection, common.Ja2RunFlowTable, &runFlowDataList, columns, conditions)
	if err != nil {
		return nil, fmt.Errorf("FetchColumnsOfMultipleRow() failed, err: [%v]", err)
	}
	for _, runFlowData := range runFlowDataList {
		prevJobIDList = append(prevJobIDList, runFlowData.StartInnerJobID)
	}
	return prevJobIDList, nil
}

// end of Ja2RunFlowTable data fetching *****************************************************************

// start of ja_2_parameter_table data fetching *****************************************************************
func GetParameterData(connection database.DBConnection, parameterName string) (common.ParameterTable, error) {
	var parameterData common.ParameterTable
	queryExt := fmt.Sprintf("parameter_name = '%s'", parameterName)
	if err := FetchSingleRow(connection, common.Ja2ParameterTable, &parameterData, queryExt); err != nil {
		return parameterData, fmt.Errorf("FetchSingleRow() failed, err: [%v]", err)
	}
	if strings.TrimSpace(parameterData.ParameterName) == "" {
		return parameterData, fmt.Errorf("no available records for parameter_name: %s", parameterName)
	}
	return parameterData, nil
}

// end of ja_2_parameter_table data fetching *****************************************************************

// start of ja_2_calendar_control_table data fetching *****************************************************************
func GetUpdateDateAndTzFromCalenderControlTableByCalendarIdAndValidFlag(
	connection database.DBConnection,
	calendarId string,
	validFlag common.Flag,
) (string, string, error) {

	var calendarControlData common.CalendarControlTable
	var columns, conditions []string

	columns = append(columns, "update_date")
	columns = append(columns, "time_zone")

	conditions = append(conditions, fmt.Sprintf("calendar_id = '%s'", calendarId))
	conditions = append(conditions, fmt.Sprintf("valid_flag = %d", validFlag))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2CalendarControlTable, &calendarControlData, columns, conditions); err != nil {
		return "", "", fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}

	return calendarControlData.UpdateDate, calendarControlData.TimeZone, nil
}

//end of ja_2_calendar_control_table data fetching *****************************************************************

// Start of ja_2_calendar_detail_table data fetching *****************************************************************
func GetActiveStatusFromCalendarDetailTableByCalendarIdUpdateDateAndOperatingDate(
	connection database.DBConnection,
	calendarId string,
	updateDate string,
	operatingDate string,
) (common.StatusType, error) {

	var calendarDetailData common.CalendarDetailTable
	var columns, conditions []string
	var status common.StatusType
	columns = append(columns, "update_date")

	conditions = append(conditions, fmt.Sprintf("calendar_id = '%s'", calendarId))
	conditions = append(conditions, fmt.Sprintf("update_date = %s", updateDate))
	conditions = append(conditions, fmt.Sprintf("operating_date = %s", operatingDate))
	if err := FetchColumnsOfSingleRow(
		connection, common.Ja2CalendarDetailTable, &calendarDetailData, columns, conditions); err != nil {
		return -1, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	if calendarDetailData.UpdateDate != "" {
		status = 1
	} else {
		status = 0
	}
	return status, nil
}

//end of ja_2_calendar_detail_table data fetching *****************************************************************

// start of  ja_2_run_jobnet_variable_table data fetching *****************************************************************
func GetJobnetVariableDataByInnerJobnetId(
	connection database.DBConnection,
	innerJobnetId uint64,
) (common.RunJobnetVariableTable, error) {

	var jobnetVariableData common.RunJobnetVariableTable

	queryExt := fmt.Sprintf("inner_jobnet_id = '%d'", innerJobnetId)
	err := FetchSingleRow(connection, common.Ja2RunJobnetVariableTable, &jobnetVariableData, queryExt)
	if err != nil {
		return jobnetVariableData, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	} else if jobnetVariableData.InnerJobnetID == 0 {
		return jobnetVariableData, fmt.Errorf("fetched record is considered empty")
	}
	return jobnetVariableData, nil
}

//end of  ja_2_run_jobnet_variable_table data fetching *****************************************************************

// start of record count fetching by table name and columns *****************************************************************
func GetRecordCountFromTableByColumn(
	connection database.DBConnection,
	tableName common.TableName,
	conditions []string,
) (int, error) {
	var columns []string
	countStruct := struct {
		Count int `json:"count"`
	}{}

	columns = append(columns, "count(*) as count")
	if err := FetchColumnsOfSingleRow(
		connection, tableName, &countStruct, columns, conditions); err != nil {
		return -1, fmt.Errorf("FetchColumnsOfSingleRow() failed, err: [%v]", err)
	}
	return countStruct.Count, nil
}

//end of record count fetching by table name and columns *****************************************************************

// start of ja_2_run_time_out_table data fetching *****************************************************************
func GetRunTimeoutData(
	connection database.DBConnection,
	innerJobId uint64,
) (common.RunTimeoutTable, error) {
	var timeoutTable common.RunTimeoutTable
	queryExt := fmt.Sprintf("inner_job_id = %d", innerJobId)
	if err := FetchSingleRow(connection, common.Ja2RunTimeOutTable, &timeoutTable, queryExt); err != nil {
		return timeoutTable, fmt.Errorf("FetchSingleRow() failed for inner_job_id %d: %w", innerJobId, err)
	}
	return timeoutTable, nil
}

// end of ja_2_run_time_out_table data fetching *****************************************************************

// start of run flow table and run job table data fetching *****************************************************************
func GetNextFlowJobIDListByStartInnerJobIdAndLesserRunCount(
	connection database.DBConnection,
	innerJobId uint64,
	runCount int,
) ([]uint64, error) {
	var runJobIdList []common.RunJobTable
	var nextJobIDList []uint64
	var columns, conditions []string

	columns = append(columns, "inner_job_id")
	conditions = append(conditions, fmt.Sprintf(
		"inner_job_id in (select end_inner_job_id from %s where start_inner_job_id = %d)",
		common.Ja2RunFlowTable, innerJobId))
	conditions = append(conditions, fmt.Sprintf("(run_count < %d)", runCount))

	err := FetchColumnsOfMultipleRow(connection, common.Ja2RunJobTable, &runJobIdList, columns, conditions)
	if err != nil {
		return nil, fmt.Errorf("FetchColumnsOfMultipleRow() failed, err: [%v]", err)
	}
	for _, runFlowData := range runJobIdList {
		nextJobIDList = append(nextJobIDList, runFlowData.InnerJobID)
	}
	return nextJobIDList, nil
}

// end of run flow table and run job table data fetching *****************************************************************
