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

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

func JaScheduleCheckTime(dbcon database.DBConnection, scheduleId string, updatedDate string, operatingDate string, bootTime string) (bool, error) {
	// 1. Check ja_2_schedule_control_table for valid_flag = 1
	query := fmt.Sprintf("SELECT valid_flag FROM ja_2_schedule_control_table WHERE schedule_id = '%s' AND update_date = '%s' AND valid_flag = 1", scheduleId, updatedDate)
	result, err := dbcon.Select(query)
	if err != nil {

		return false, fmt.Errorf(sqlErrFmt, "ja_2_schedule_control_table", query, err)
	}
	if !result.HasNextRow() {
		result.Free()
		return false, nil
	}
	row, err := result.Fetch()
	if err != nil {
		result.Free()
		return false, fmt.Errorf(fetchErrFmt, "ja_2_schedule_control_table", err)
	}
	result.Free()

	// 2. Get calendar_id from ja_schedule_detail_table
	query = fmt.Sprintf("SELECT calendar_id FROM ja_2_schedule_detail_table WHERE schedule_id = '%s' AND update_date = '%s' AND boot_time = '%s'", scheduleId, updatedDate, bootTime)
	result, err = dbcon.Select(query)
	if err != nil {
		return false, fmt.Errorf(sqlErrFmt, "ja_2_schedule_detail_table", query, err)
	}
	if !result.HasNextRow() {
		result.Free()
		return false, nil
	}
	row, err = result.Fetch()
	if err != nil {
		result.Free()
		return false, fmt.Errorf(fetchErrFmt, "ja_2_schedule_detail_table", err)
	}
	calendarId := fmt.Sprintf("%v", row["calendar_id"])
	result.Free()

	// 3. Get update_date from ja_2_calendar_control_table for calendar_id and valid_flag = 1
	query = fmt.Sprintf("SELECT update_date FROM ja_2_calendar_control_table WHERE calendar_id = '%s' AND valid_flag = 1", calendarId)
	result, err = dbcon.Select(query)
	if err != nil {
		return false, fmt.Errorf(sqlErrFmt, "ja_2_calendar_control_table", query, err)
	}
	if !result.HasNextRow() {
		result.Free()
		return false, nil
	}
	row, err = result.Fetch()
	if err != nil {
		result.Free()
		return false, fmt.Errorf(fetchErrFmt, "ja_2_calendar_control_table", err)
	}
	calUpdateDate := fmt.Sprintf("%v", row["update_date"])
	result.Free()

	// 4. Check for calendar detail for the operating date
	query = fmt.Sprintf("SELECT calendar_id FROM ja_2_calendar_detail_table WHERE calendar_id = '%s' AND update_date = '%s' AND operating_date = '%s'", calendarId, calUpdateDate, operatingDate)
	result, err = dbcon.Select(query)
	if err != nil {
		return false, fmt.Errorf(sqlErrFmt, "ja_2_calendar_detail_table", query, err)
	}
	if !result.HasNextRow() {
		result.Free()
		return false, nil
	}
	result.Free()

	return true, nil // All checks passed!
}

func GetScheduleLoadSpan(dbcon database.DBConnection) (int, error) {
	span := DEFAULT_LOAD_SPAN

	query := fmt.Sprintf("SELECT value FROM ja_2_parameter_table WHERE parameter_name = '%s'", JOBNET_LOAD_SPAN)
	result, err := dbcon.Select(query)
	if err != nil {
		return span, fmt.Errorf(sqlErrFmt, "ja_2_parameter_table", query, err)
	}
	defer result.Free()

	if !result.HasNextRow() {
		return span, nil
	}

	row, err := result.Fetch()
	if err != nil {
		return span, fmt.Errorf(fetchErrFmt, "ja_2_parameter_table", err)
	}

	value := fmt.Sprintf("%v", row["value"])

	parsed, err := strconv.Atoi(value)
	if err == nil {
		span = parsed
	}
	if span < MIN_LOAD_SPAN {
		span = MIN_LOAD_SPAN
	}
	if span > MAX_LOAD_SPAN {
		span = MAX_LOAD_SPAN
	}

	return span, nil
}

func getCalendarId(dbcon database.DBConnection, scheduleId string) (string, error) {
	query := fmt.Sprintf("SELECT calendar_id FROM ja_2_schedule_detail_table WHERE schedule_id = '%s'", scheduleId)
	result, err := dbcon.Select(query)
	if err != nil {
		return "", fmt.Errorf(sqlErrFmt, "ja_2_schedule_detail_table", query, err)
	}
	defer result.Free()
	if !result.HasNextRow() {
		return "", nil // not found
	}
	row, err := result.Fetch()
	if err != nil {
		return "", fmt.Errorf(fetchErrFmt, "ja_2_schedule_detail_table", err)
	}
	return fmt.Sprintf("%v", row["calendar_id"]), nil
}

func getTimezone(dbcon database.DBConnection, calendarId string) (string, error) {
	query := fmt.Sprintf("SELECT time_zone FROM ja_2_calendar_control_table WHERE calendar_id = '%s'", calendarId)
	result, err := dbcon.Select(query)
	if err != nil {
		return "", fmt.Errorf(sqlErrFmt, "ja_2_calendar_control_table", query, err)
	}
	defer result.Free()
	if !result.HasNextRow() {
		return "", nil // not found
	}
	row, err := result.Fetch()
	if err != nil {
		return "", fmt.Errorf(fetchErrFmt, "ja_2_calendar_control_table", err)
	}
	return fmt.Sprintf("%v", row["time_zone"]), nil
}

func calculateBootTime(loadSpan, loadShiftTime int, timezone string) (sDate, sTime string, futureUnixTime int64, err error) {
	now := time.Now().Unix()
	offsetSeconds := int64(loadSpan-loadShiftTime-1) * 60
	futureUnixTime = now + offsetSeconds

	loc, err := time.LoadLocation(timezone)
	if err != nil {
		return "", "", 0, err
	}
	tm := time.Unix(futureUnixTime, 0).In(loc)
	sDate = fmt.Sprintf("%04d%02d%02d", tm.Year(), tm.Month(), tm.Day())
	sTime = fmt.Sprintf("%02d%02d", tm.Hour(), tm.Minute())
	return sDate, sTime, futureUnixTime, nil
}

func JaCheckLoader(dbcon database.DBConnection, loadShiftTime int) error {
	fmt.Printf("%d:%s [DEBUG] In JaCheckLoader().\n", os.Getppid(), time.Now().Format("20060102:150405.000"))

	loadSpan, err := GetScheduleLoadSpan(dbcon)
	if err != nil {
		return fmt.Errorf("[ERROR] : %w", err)
	}

	query := "SELECT schedule_id, jobnet_id, update_date from ja_2_schedule_jobnet_table"
	result, err := dbcon.Select(query)
	if err != nil {
		return fmt.Errorf(sqlErrFmt, "ja_2_schedule_jobnet_table", query, err)
	}
	defer result.Free()

	for result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			return fmt.Errorf(fetchErrFmt, "ja_2_schedule_jobnet_table", err)
		}

		scheduleId := fmt.Sprintf("%v", row["schedule_id"])
		jobnetId := fmt.Sprintf("%v", row["jobnet_id"])
		updateDate := fmt.Sprintf("%v", row["update_date"])

		calendarId, err := getCalendarId(dbcon, scheduleId)
		if err != nil {
			return fmt.Errorf("[ERROR] : %w", err)
		}
		if calendarId == "" {
			continue // No calendar, skip
		}

		timezone, err := getTimezone(dbcon, calendarId)
		if err != nil {
			return fmt.Errorf("[ERROR] : %w", err)
		}
		if timezone == "" {
			continue // No timezone, skip
		}
		fmt.Println("Timezone:", timezone)

		sDate, sTime, futureUnixTime, err := calculateBootTime(loadSpan, loadShiftTime, timezone)
		if err != nil {
			return fmt.Errorf("[ERROR] : %w", err)
		}
		fmt.Println("BootTIme:", sTime)

		checkStatus, err := JaScheduleCheckTime(dbcon, scheduleId, updateDate, sDate, sTime)
		if err != nil {
			return fmt.Errorf("[ERROR] : %w", err)
		}
		if !checkStatus {
			continue
		}

		queryLoad := fmt.Sprintf("SELECT start_time FROM ja_2_run_jobnet_summary_table WHERE jobnet_id = '%s' AND scheduled_time = '%d' AND run_type = %d",
			jobnetId, futureUnixTime, common.JA_JOBNET_RUN_TYPE_NORMAL)
		resultLoad, err := dbcon.Select(queryLoad)
		if err != nil {
			return fmt.Errorf(sqlErrFmt, "ja_2_run_jobnet_summary_table", queryLoad, err)
		}
		if !resultLoad.HasNextRow() {
			resultLoad.Free()
			schTimeStr := strconv.FormatInt(futureUnixTime, 10)
			err := JaSender(dbcon, JA_SENDER_LOADER, scheduleId, jobnetId, schTimeStr, "")
			if err != nil {
				return fmt.Errorf("[ERROR] : %w", err)
			}
		} else {
			resultLoad.Free()
		}
	}

	return nil
}
