package main

import (
	"bufio"
	"bytes"
	"encoding/csv"
	"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 Job Log"

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")
	from_time       = flag.String("s", "", "From time YYYYMMDD or YYYYMMDDHHMM")
	to_time         = flag.String("e", "", "To time YYYYMMDD or YYYYMMDDHHMM")
	jobnetId        = flag.String("n", "", "Jobnet ID")
	jobid           = flag.String("j", "", "Job ID")
	target_user     = flag.String("u", "", "Target User")
	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 == "" {
		fmt.Fprintln(os.Stderr, "Error: Missing required parameters")
		jobargHelp()
		os.Exit(1)
	}

	if *registry_number == "" {

		if *from_time != "" {
			if err := validateStartTime(*from_time); err != nil {
				fmt.Fprintf(os.Stderr, "Error: Invalid from time format: %v\n", err)
				os.Exit(1)
			}
		} else {
			fmt.Fprintln(os.Stderr, "Missing required parameters from time")
			os.Exit(1)
		}

		if *to_time != "" {
			if err := validateStartTime(*to_time); err != nil {
				fmt.Fprintf(os.Stderr, "Error: Invalid to time format: %v\n", err)
				os.Exit(1)
			}
		} else {
			fmt.Fprintln(os.Stderr, "Missing required parameters to time")
			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, *from_time, *to_time, *jobnetId, *jobid, *target_user, *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)
	respBodyJobLog, err := sendToPHPAPIJobLog(*apiURL, payload)
	if err != nil {
		log.Fatalf("Error sending request: %v", err)
	}
	defer respBodyJobLog.Close()

	logger.WriteLogHelper(logger.LogLevelDebug, "Request to PHP api, apiURL: %s, payload: %s", *apiURL, payload)

	err = StreamAndPrintLogs(respBodyJobLog)
	if err != nil {
		log.Fatalf("Stream error: %v", err)
	}

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

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

func jobargHelp() {
	fmt.Println(titleMessage)
	fmt.Println("Usage:jobarg_joblogput [-hV] -z <server> [-p <port>] -U <user-name> -P <password> [-s <YYYYMMDD>|<YYYYMMDDHHMM>] [-e <YYYYMMDD>|<YYYYMMDDHHMM>] [-n <jobnet-id>] [-j <job-id>] [-u <target-user>] [-r <registry-number>]")
	fmt.Println("Options:")
	fmt.Println("  -s <YYYYMMDD>|<YYYYMMDDHHMM>     Specify from time")
	fmt.Println("  -e <YYYYMMDD>|<YYYYMMDDHHMM>     Specify to time")
	fmt.Println("  -n <jobnet-id>                   Specify Jobnet Id")
	fmt.Println("  -j <job-id>                      Specify Job Id")
	fmt.Println("  -u <target-user>                 Specify target user")
	fmt.Println("  -r  <registry-number>            Specify registry_number")
	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 {
	//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 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, from_time string, to_time string, jobnetId string, jobid string, target_user string, registry_number string, auth string) (string, error) {

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

	// Add optional fields
	if jobnetId != "" {
		data["jobnetId"] = jobnetId
	}

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

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

	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_joblogput",
			"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 sendToPHPAPIJobLog(url, payload string) (io.ReadCloser, error) {
	resp, err := http.Post(url, "application/json", bytes.NewBufferString(payload))
	if err != nil {
		return nil, err
	}

	// DO NOT CLOSE resp.Body here — return it to be read/closed later
	return resp.Body, nil
}

// Optional: define constants or mapping if needed for fixed values like "run type", "public flag", etc.

func formatLogDate(logDate string) string {
	// If your logDate is in "20250613104643679" (yyyyMMddHHmmssSSS) format, convert it to proper time
	if len(logDate) < 17 {
		return logDate // fallback if invalid format
	}

	parsed, err := time.Parse("20060102150405000", logDate)
	if err != nil {
		return logDate // fallback
	}
	return parsed.Format("2006/01/02 15:04:05.000")
}

func PrintCSVLogList(logList []common.JobLog) {
	writer := csv.NewWriter(os.Stdout)
	defer writer.Flush()

	// Write header
	writer.Write([]string{
		"log date", "inner jobnet main id", "inner jobnet id", "run type", "public flag",
		"jobnet id", "job id", "message id", "message", "jobnet name", "job name",
		"user name", "update date", "return code",
	})

	for _, log := range logList {
		jobId := ""
		if log.JobID != nil {
			jobId = *log.JobID
		}

		jobName := ""
		if log.JobName != nil {
			jobName = *log.JobName
		}

		writer.Write([]string{
			formatLogDate(log.LogDate),
			log.InnerJobnetMainID,
			log.RunType,
			log.PublicFlag,
			log.JobnetID,
			jobId,
			log.MessageID,
			log.Message,
			log.JobnetName,
			jobName,
			log.UserName,
			log.UpdateDate,
			log.ExistCd,
		})
	}
}

func StreamAndPrintLogs(respBody io.Reader) error {
	scanner := bufio.NewScanner(respBody)

	// Write CSV header
	writer := csv.NewWriter(os.Stdout)
	defer writer.Flush()
	writer.Write([]string{
		"log date", "inner jobnet main id", "inner jobnet id", "run type", "public flag",
		"jobnet id", "job id", "message id", "message", "jobnet name", "job name",
		"user name", "update date", "return code",
	})

	// for scanner.Scan() {
	// 	line := scanner.Text()

	// 	var meta struct {
	// 		JobArranger struct {
	// 			Result bool          `json:"result"`
	// 			Log    common.JobLog `json:"log"`
	// 		} `json:"JobArranger"`
	// 	}

	// 	if err := json.Unmarshal([]byte(line), &meta); err != nil {
	// 		log.Printf("Error parsing line: %v\n", err)
	// 		continue
	// 	}

	// 	log := meta.JobArranger.Log

	// 	jobId := ""
	// 	if log.JobID != nil {
	// 		jobId = *log.JobID
	// 	}

	// 	jobName := ""
	// 	if log.JobName != nil {
	// 		jobName = *log.JobName
	// 	}

	// 	writer.Write([]string{
	// 		formatLogDate(log.LogDate),
	// 		log.InnerJobnetMainID,
	// 		log.RunType,
	// 		log.PublicFlag,
	// 		log.JobnetID,
	// 		jobId,
	// 		log.MessageID,
	// 		log.Message,
	// 		log.JobnetName,
	// 		jobName,
	// 		log.UserName,
	// 		log.UpdateDate,
	// 		log.ExistCd,
	// 	})
	// }

	for scanner.Scan() {
		line := scanner.Text()

		if len(line) == 0 || line[0] != '{' {
			log.Printf("Skipping non-JSON line: %s\n", line)
			continue
		}

		var meta struct {
			JobArranger struct {
				Result bool          `json:"result"`
				Log    common.JobLog `json:"log"`
			} `json:"JobArranger"`
		}

		if err := json.Unmarshal([]byte(line), &meta); err != nil {
			log.Printf("Error parsing line: %v, line: %s\n", err, line)
			continue
		}

		log := meta.JobArranger.Log

		jobId := ""
		if log.JobID != nil {
			jobId = *log.JobID
		}

		jobName := ""
		if log.JobName != nil {
			jobName = *log.JobName
		}

		writer.Write([]string{
			formatLogDate(log.LogDate),
			log.InnerJobnetMainID,
			log.InnerJobnetID,
			log.RunType,
			log.PublicFlag,
			log.JobnetID,
			jobId,
			log.MessageID,
			log.Message,
			log.JobnetName,
			jobName,
			log.UserName,
			log.UpdateDate,
			log.ExistCd,
		})
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scanner error: %w", err)
	}

	return 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.LogList = []common.JobLog{resp.JobArranger.Log}
	}

	return result, nil
}

func handleResponseFromWeb(response string, use string) (interface{}, error) {
	scanner := bufio.NewScanner(strings.NewReader(response))
	var logs []common.JobLog

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		// Remove prefix like "Response " if present
		idx := strings.Index(line, "{")
		if idx == -1 {
			continue
		}
		jsonPart := line[idx:]

		var resp common.FullResponse
		if err := json.Unmarshal([]byte(jsonPart), &resp); err != nil {
			// Skip invalid JSON lines but log them
			log.Printf("Skipping invalid JSON line: %v, raw line: %s", err, jsonPart)
			continue
		}

		logs = append(logs, resp.JobArranger.Log)
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error reading response: %w", err)
	}

	jobResult := true
	return common.ZabbixOrJobResult{
		JobResult: &jobResult,
		LogList:   logs,
	}, 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)
	}()
}

// Export logs to CSV while streaming
func StreamLogsAndExportCSV(logList []common.JobLog, csvFilePath string) error {
	file, err := os.Create(csvFilePath)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// Write header
	writer.Write([]string{
		"log date", "inner jobnet main id", "jobnet id", "run type", "public flag",
		"job id", "message id", "message", "jobnet name", "job name",
		"user name", "update date", "return code",
	})

	// Stream to console and write to CSV
	for _, logEntry := range logList {
		jobID := ""
		if logEntry.JobID != nil {
			jobID = *logEntry.JobID
		}
		jobName := ""
		if logEntry.JobName != nil {
			jobName = *logEntry.JobName
		}

		row := []string{
			logEntry.LogDate,
			logEntry.InnerJobnetMainID,
			logEntry.JobnetID,
			logEntry.RunType,
			logEntry.PublicFlag,
			jobID,
			logEntry.MessageID,
			logEntry.Message,
			logEntry.JobnetName,
			jobName,
			logEntry.UserName,
			logEntry.UpdateDate,
			logEntry.ExistCd,
		}

		// Print to console
		//fmt.Println(strings.Join(row, ","))

		// Write to CSV
		writer.Write(row)
		writer.Flush()
	}

	return nil
}
