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

const titleMessage = "Job Arranger Jobnet Execution"

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")
	jobnetidFlag = flag.String("j", "", "Jobnet ID")
	startTime    = flag.String("t", "", "Start time (YYYYMMDDHHMM)")
	envVars      = flag.String("E", "", "Environment variables (comma separated)")
	env1Vars     = flag.String("e", "", "Environment variables for JA functions (comma separated)")
	deterrence   = flag.Bool("D", false, "Enable double registration deterrence")
	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 == "" || *jobnetidFlag == "" {
		fmt.Fprintln(os.Stderr, "Error: Missing required parameters")
		jobargHelp()
		os.Exit(1)
	}

	if *startTime != "" {
		if err := validateStartTime(*startTime); err != nil {
			fmt.Fprintf(os.Stderr, "Error: Invalid start 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)

	payload, err = buildJSONPayload(
		*userFlag, *passFlag, *jobnetidFlag,
		resZabbix.ZabbixAuth, *startTime, *deterrence, *envVars, *env1Vars,
	)
	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 == false {
			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")
	}

	log.Printf("Registry number: %s", res.RegistryNumber)

	// 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(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)
	}
}

// ----------------- Utility Functions -----------------

func jobargHelp() {
	fmt.Println(titleMessage)
	fmt.Println("Usage: jobarg_exec -z <server> -U <user> -P <pass> -j <jobnet-id> [options]")
	fmt.Println("Options:")
	fmt.Println("  -t <YYYYMMDDHHMM>      Specify start time")
	fmt.Println("  -E <ENV,...>           Environment variables to send")
	fmt.Println("  -e <ENV,...>           Environment variables for JA functions")
	fmt.Println("  -D                     Enable double registration deterrence")
	fmt.Println("  -h                     Show help")
	fmt.Println("  -V                     Show version")
}

func jobargVersion() {
	fmt.Printf("%s v%s (revision %s) (%s)\n", titleMessage, common.JobargVersion, common.JobargRevision, common.JobargRevDate)
	fmt.Printf("Compilation time: %s\n", time.Now().Format("2006-01-02 15:04:05"))
}

func validateStartTime(t string) error {
	if len(t) != 12 {
		return fmt.Errorf("start time must be 12 characters in YYYYMMDDHHMM format")
	}
	_, err := time.Parse("200601021504", t)
	return err
}

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 buildJSONPayload(user, pass, jobnetid, auth, start string, deterrence bool, envVars, env1Vars string) (string, error) {
	// Optional environment variable parser
	parseEnvVars := func(vars string) (map[string]string, error) {
		result := map[string]string{}
		if vars == "" {
			return result, nil
		}
		for _, item := range strings.Split(vars, ",") {
			item = strings.TrimSpace(item)
			val := os.Getenv(item)
			if val == "" {
				return nil, fmt.Errorf("environment variable [%s] does not exist", item)
			}
			result[item] = val
		}
		return result, nil
	}

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

	// Add optional fields
	if start != "" {
		data["start_time"] = start
		data["deterrence"] = map[bool]string{true: "1", false: "0"}[deterrence]
	}

	if envMap, err := parseEnvVars(envVars); err != nil {
		return "", err
	} else if len(envMap) > 0 {
		data["env"] = envMap
	}

	if env1Map, err := parseEnvVars(env1Vars); err != nil {
		return "", err
	} else if len(env1Map) > 0 {
		data["env1"] = env1Map
	}

	// 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": "jobnetrun",
			"data":   data,
		},
	}

	// Marshal to JSON
	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 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 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.RegistryNumber = resp.JobArranger.RegistryNumber
	}

	return result, nil
}

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