package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

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

var (
	schemeFlag      = flag.Bool("S", false, "URL scheme (http or https)")
	apiURL          = flag.String("z", "", "Job Arranger Manager API URL")
	port            = flag.String("p", "", "Specify port number of Job Arranger Manager")
	userFlag        = flag.String("U", "", "User name")
	passFlag        = flag.String("P", "", "Password")
	jobnetId_jobId  = flag.String("j", "", "Jobnet ID / Job ID")
	start_time      = flag.String("t", "", "start-time <YYYYMMDD> or <YYYYMMDDHHMM>")
	registry_number = flag.String("r", "", "Registry_Number")
	helpFlag        = flag.Bool("h", false, "Show help")
	versionFlag     = flag.Bool("V", false, "Show version")
)

func main() {
	flag.Parse()

	if *helpFlag {
		jobargHelp()
		return
	}
	if *versionFlag {
		jobargVersion()
		return
	}

	if *apiURL == "" || *userFlag == "" || *passFlag == "" || *jobnetId_jobId == "" {
		fmt.Fprintln(os.Stderr, "Error: Missing required parameters")
		jobargHelp()
		os.Exit(1)
	}

	if *start_time != "" {
		if err := validateStartTime(*start_time); err != nil {
			fmt.Fprintf(os.Stderr, "Error: Invalid to time format: %v\n", err)
			os.Exit(1)
		}
	}

	if !strings.HasPrefix(*apiURL, "http://") && !strings.HasPrefix(*apiURL, "https://") {
		host := *apiURL
		port := *port

		if port == "" {
			// set default port based on scheme
			switch *schemeFlag {
			case false:
				port = "80"
			case true:
				port = "443"
			default:
				port = "80" // fallback default
			}
		}

		// append port if not empty
		if port != "" {
			host = fmt.Sprintf("%s:%s", host, port)
		}

		scheme := "http"
		if *schemeFlag {
			scheme = "https"
		}

		*apiURL = fmt.Sprintf("%s://%s/jobarranger/api/JobargModule/handleRequest", scheme, host)

	}

	// Init logger
	if err := logger.InitLogger(
		"",
		"",
		"console",
		10*1024*1024,
		int(logger.LogLevelInfo),
		logger.TargetTypeModule,
	); err != nil {
		fmt.Fprintf(os.Stderr, "failed to initialize logger: %v", err)
		os.Exit(1)
	}

	logger.WriteLogHelper(logger.LogLevelDebug, "DEBUG LOG TESTING")

	// Build json payload for zabbix login
	payload, err := buildJSONPayloadForZabbixLogin(*userFlag, *passFlag)
	if err != nil {
		log.Fatalf("Error building payload: %v", err)
	}

	setupSignalHandler()

	respBody, err := sendToPHPAPI(*apiURL, payload)
	if err != nil {
		log.Fatalf("Error sending request: %v", err)
	}

	resRaw, err := checkResponseFromJazWeb(respBody, "zabbix")
	if err != nil {
		if errResp, ok := resRaw.(common.ErrorResponseWithMsg); ok {
			fmt.Printf("Error type: %s, message: %s\n", errResp.Error.Type, errResp.Error.Message)
		} else {
			fmt.Fprintf(os.Stderr, "Unexpected error: %v\n", err)
		}
		os.Exit(1)
	}

	resZabbix, ok := resRaw.(common.ZabbixOrJobResult)
	if !ok {
		fmt.Fprintf(os.Stderr, "Response is not of type ZabbixOrJobResult\n")
		os.Exit(1)
	}

	logger.WriteLogHelper(logger.LogLevelDebug, "Response from PHP API: %+v", resZabbix)

	// Split the jobnetId_jobId by '/'
	if *jobnetId_jobId != "" {
		parts := strings.SplitN(*jobnetId_jobId, "/", 2)
		if len(parts) == 2 {
			jobnetId := parts[0]
			jobId := parts[1]
			// fmt.Printf("Jobnet ID: %s\n", jobnetId)
			// fmt.Printf("Job ID: %s\n", jobId)

			payload, err = buildJSONPayload(
				*userFlag, *passFlag, *start_time, jobnetId, jobId, *registry_number, resZabbix.ZabbixAuth)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error building JSON payload: %v\n", err)
				os.Exit(1)
			}

			setupSignalHandler()

			// Send JSON to PHP API
			logger.WriteLogHelper(logger.LogLevelDebug, "apiURL: %s, payload: %s", *apiURL, payload)
			respBody, err = sendToPHPAPI(*apiURL, payload)
			if err != nil {
				log.Fatalf("Error sending request: %v", err)
			} else {
				logger.WriteLogHelper(logger.LogLevelDebug, "Request to PHP api, apiURL: %s, payload: %s", *apiURL, payload)
			}

			// Process response
			logger.WriteLogHelper(logger.LogLevelDebug, "Response from PHP API: %s", respBody)

			resRaw, err = checkResponseFromJazWeb(respBody, "jaz")
			if err != nil {
				if errResp, ok := resRaw.(common.ErrorResponseWithMsg); ok {
					fmt.Printf("Error type: %s, message: %s\n", errResp.Error.Type, errResp.Error.Message)
				} else {
					fmt.Fprintf(os.Stderr, "Unexpected error: %v\n", err)
				}
				os.Exit(1)
			}

			res, ok := resRaw.(common.ZabbixOrJobResult)
			if !ok {
				fmt.Fprintf(os.Stderr, "Response is not of type ZabbixOrJobResult\n")
				os.Exit(1)
			} else {
				if !*res.JobResult {
					log.Fatalf("registry number not found")
				}
			}

			// Now safely use the result
			if res.JobResult == nil || !*res.JobResult {
				log.Fatalf("Job execution failed or registry number missing")
			}

			if len(res.InnerJobnetIdList.InnerJobnetId) > 0 {
				for i, id := range res.InnerJobnetIdList.InnerJobnetId {
					fmt.Printf("InnerJobnetId[%d]: %s\n", i, id)
				}

				// prepare logout payload
				logoutPayload, err := buildJSONPayloadForZabbixLogout(resZabbix.ZabbixAuth)
				if err != nil {
					log.Fatalf("Error building payload: %v", err)
				}

				setupSignalHandler()
				// send logout request
				respLogout, err := sendToPHPAPI(*apiURL, logoutPayload)
				if err != nil {
					log.Fatalf("Error sending request: %v", err)
				}

				logger.WriteLogHelper(logger.LogLevelDebug, "Response from PHP API: %s", respLogout)

				resRaw, err = checkResponseFromJazWeb(respLogout, "zabbix")
				if err != nil {
					if errResp, ok := resRaw.(common.ErrorResponseWithMsg); ok {
						fmt.Printf("Error type: %s, message: %s\n", errResp.Error.Type, errResp.Error.Message)
					} else {
						fmt.Fprintf(os.Stderr, "Unexpected error: %v\n", err)
					}
					os.Exit(1)
				}

				resZabbix, ok = resRaw.(common.ZabbixOrJobResult)
				if !ok {
					fmt.Fprintf(os.Stderr, "Response is not of type ZabbixOrJobResult\n")
					os.Exit(1)
				}
			}

		} else {
			fmt.Println("Invalid format for -j. Expected 'jobnetId/jobId'")
		}
	}

}

func validateStartTime(t string) error {
	//log.Printf("Time ==========>: %s", t)
	switch len(t) {
	case 8: // YYYYMMDD
		_, err := time.Parse("20060102", t)
		return err
	case 12: // YYYYMMDDHHMM
		_, err := time.Parse("200601021504", t)
		return err
	default:
		return fmt.Errorf("time must be in YYYYMMDD (8 chars) or YYYYMMDDHHMM (12 chars) format")
	}
}

func jobargHelp() {
	fmt.Println("Job Arranger Jobnet/Job Release")
	fmt.Println("Usage: jobarg_release -z <manager-ip> -U <user> -P <pass> -j <jobnet-id/job-id> -t <YYYYMMDD>|<YYYYMMDDHHMM> -r <registry-number>")
}

func jobargVersion() {
	fmt.Printf("Job Arranger Jobnet Release v%s (rev %s) (%s)\n", common.JobargVersion, common.JobargRevision, common.JobargRevDate)
	fmt.Printf("Compiled at %s\n", time.Now().Format("2006-01-02 15:04:05"))
}

func buildJSONPayloadForZabbixLogin(user, pass string) (string, error) {
	payload := common.RequestPayload{
		Zabbix: common.ZabbixRequest{
			JSONRPC: "2.0",
			Method:  "user.login",
			Params: &common.ZabbixParams{ // <-- note the &
				Username: user,
				Password: pass,
			},
			ID: 1,
		},
		JobArranger: map[string]any{},
	}

	bytes, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

func buildJSONPayloadForZabbixLogout(auth string) (string, error) {
	payload := common.RequestPayload{
		Zabbix: common.ZabbixRequest{
			JSONRPC: "2.0",
			Method:  "user.logout",
			Params:  []any{},
			Auth:    auth,
			ID:      1,
		},
		JobArranger: map[string]any{},
	}

	bytes, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

func buildJSONPayload(user, pass, start_time string, jobnetId string, jobId string, registry_number string, auth string) (string, error) {

	// Base data map for JobArranger.data
	data := map[string]interface{}{
		"username":        user,
		"password":        pass,
		"jobnetid":        jobnetId,
		"jobid":           jobId,
		"start_time":      start_time,
		"registry_number": registry_number,
	}

	if jobId != "" {
		data["jobid"] = jobId
	}

	if registry_number != "" {
		data["registry_number"] = registry_number
	}

	// Construct full payload
	payload := map[string]interface{}{
		"Zabbix": map[string]interface{}{
			"jsonrpc": "2.0",
			"method":  "execution",
			"params":  nil,
			"auth":    auth,
			"id":      1,
		},
		"JobArranger": map[string]interface{}{
			"method": "jobarg_jobrelease",
			"data":   data,
		},
	}

	// Marshal to JSON
	bytes, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

func checkResponseFromJazWeb(response, use string) (interface{}, error) {

	// log.Printf("Response %s", response)

	var errMsg common.ErrorResponseWithMsg
	var resp common.FullResponse
	if err := json.Unmarshal([]byte(response), &errMsg); err == nil && errMsg.Error.Message != "" {
		result := common.ErrorResponseWithMsg{}
		result.Error.Type = errMsg.Error.Type
		result.Error.Message = errMsg.Error.Message
		return result, fmt.Errorf("Error message: %s and type: %s", errMsg.Error.Message, errMsg.Error.Type)
	} else if err := json.Unmarshal([]byte(response), &resp); err != nil {
		return common.ZabbixOrJobResult{}, fmt.Errorf("cannot parse JSON: %w", err)
	}

	result := common.ZabbixOrJobResult{}

	if use == "zabbix" {
		result.ZabbixAuth = resp.Zabbix.Result
	} else {
		result.JobResult = &resp.JobArranger.Result
		result.InnerJobnetIdList.InnerJobnetId = resp.JobArranger.JobReleaseInnerJobnetId
	}

	return result, nil
}

func sendToPHPAPI(url, payload string) (string, error) {
	resp, err := http.Post(url, "application/json", bytes.NewBufferString(payload))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	return string(body), nil
}

func checkResponse(response string) error {
	type ResponseData struct {
		Result  int    `json:"result"`
		Message string `json:"message"`
	}
	type Response struct {
		Kind    string       `json:"kind"`
		Version int          `json:"version"`
		Data    ResponseData `json:"data"`
	}

	var resp Response
	if err := json.Unmarshal([]byte(response), &resp); err != nil {
		return fmt.Errorf("cannot parse JSON: %w", err)
	}
	if resp.Data.Result != 0 {
		return fmt.Errorf("API failed: %s", resp.Data.Message)
	}
	return nil
}

func setupSignalHandler() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	go func() {
		<-c
		log.Println("Received signal, exiting...")
		os.Exit(1)
	}()
}
