package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	tcell "github.com/gdamore/tcell/v2"
	"github.com/nats-io/nats.go"
	"github.com/rivo/tview"
)

type CommandData struct {
	ToNodes          []string `json:"toNodes"`
	ToNode           string   `json:"toNode"`
	JetstreamToNode  string   `json:"jetstreamToNode,omitempty"`
	UseDetectedShell bool     `json:"useDetectedShell"`
	Method           string   `json:"method"`
	MethodArgs       []string `json:"methodArgs"`
	MethodTimeout    int      `json:"methodTimeout"`
	FromNode         string   `json:"fromNode"`
	ReplyMethod      string   `json:"replyMethod"`
	ACKTimeout       int      `json:"ACKTimeout"`
}

// Create a global variable for the output view so it can be accessed from NATS handlers
var outputView *tview.TextView
var historyList *tview.List
var filesList *tview.List
var app *tview.Application
var nc *nats.Conn

// Store form values globally
var (
	toNodes    string = "btdev1"
	methodArgs        = []string{"/bin/bash", "-c", "ls -l"}
	// Store history commands
	historyCommands []string
)

func createCommandPage() tview.Primitive {
	// Create a flex container for the entire page
	mainFlex := tview.NewFlex().SetDirection(tview.FlexRow)

	// Create form for inputs
	formFields := tview.NewForm()
	formFields.SetBorder(true).SetTitle("Command Form")

	// Create a List for command history
	historyList = tview.NewList()
	historyList.SetBorder(true)
	historyList.SetTitle("History")
	// Handle selection from history
	historyList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
		// Update Method Arg 3 field with selected command
		methodArg3Field := formFields.GetFormItemByLabel("Method Arg 3").(*tview.InputField)
		methodArg3Field.SetText(mainText)
		methodArgs[2] = mainText // Update global variable
		app.SetFocus(formFields) // Return focus to form
	})

	// Create a List for files
	filesList = tview.NewList()
	filesList.SetBorder(true)
	filesList.SetTitle("Files")
	filesList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
		// When a file is selected, update Method Arg 3 with the CTRL_FILE template
		methodArg3Field := formFields.GetFormItemByLabel("Method Arg 3").(*tview.InputField)
		methodArg3Field.SetText("{{CTRL_FILE:" + secondaryText + "}}")
		methodArgs[2] = methodArg3Field.GetText()
		app.SetFocus(formFields)
	})

	// Create a vertical flex for history and files lists
	rightPanelFlex := tview.NewFlex().
		SetDirection(tview.FlexRow).
		AddItem(historyList, 0, 1, false).
		AddItem(filesList, 0, 1, false)

	// Create a horizontal flex for the form and right panel
	topFlex := tview.NewFlex().
		AddItem(formFields, 0, 2, true).
		AddItem(rightPanelFlex, 0, 1, false)

	// Create output text view
	outputView = tview.NewTextView().
		SetDynamicColors(true).
		SetChangedFunc(func() {
			app.Draw()
		})
	outputView.SetBorder(true).SetTitle("Output")

	// Add form fields
	formFields.AddInputField("To Nodes", toNodes, 30, nil, func(text string) {
	})

	formFields.AddInputField("Jetstream To Node", "", 30, nil, func(text string) {
		// Value will be read directly from form when needed
	})

	formFields.AddCheckbox("Use Detected Shell", false, func(checked bool) {
		// Value will be read directly from form when needed
	})

	formFields.AddInputField("Method", "cliCommand", 30, nil, func(text string) {
		// Value will be read directly from form when needed
	})

	formFields.AddInputField("Method Arg 1", methodArgs[0], 30, nil, func(text string) {
		methodArgs[0] = text
	})

	formFields.AddInputField("Method Arg 2", methodArgs[1], 30, nil, func(text string) {
		methodArgs[1] = text
	})

	formFields.AddInputField("Method Arg 3", methodArgs[2], 30, nil, func(text string) {
		methodArgs[2] = text
	}).SetFieldStyle(tcell.StyleDefault.Blink(true))

	formFields.AddInputField("Method Timeout", "3", 30, nil, func(text string) {
		// Value will be read directly from form when needed
	})

	formFields.AddInputField("Reply Method", "webUI", 30, nil, func(text string) {
		// Value will be read directly from form when needed
	})

	formFields.AddInputField("ACK Timeout", "0", 30, nil, func(text string) {
		// Value will be read directly from form when needed
	})

	// Subscribe to responses
	fromNode := "btdev1"

	// Add buttons
	formFields.AddButton("Send", func() {
		// Get and save the current command to history
		currentCmd := formFields.GetFormItemByLabel("Method Arg 3").(*tview.InputField).GetText()
		if currentCmd != "" {
			// Add to history if not already present
			found := false
			for _, cmd := range historyCommands {
				if cmd == currentCmd {
					found = true
					break
				}
			}
			if !found {
				historyCommands = append([]string{currentCmd}, historyCommands...)
				historyList.Clear()
				for _, cmd := range historyCommands {
					historyList.AddItem(cmd, "", 0, nil).ShowSecondaryText(false)
				}
			}
		}

		// Get current values from form
		methodTimeout, _ := strconv.Atoi(formFields.GetFormItemByLabel("Method Timeout").(*tview.InputField).GetText())
		ackTimeout, _ := strconv.Atoi(formFields.GetFormItemByLabel("ACK Timeout").(*tview.InputField).GetText())

		// Get and clean toNodes
		toNodesText := formFields.GetFormItemByLabel("To Nodes").(*tview.InputField).GetText()
		toNodes := strings.Split(toNodesText, ",")
		// Trim spaces and filter out empty nodes
		// cleanedToNodes := []string{}
		// for _, node := range toNodes {
		// 	if trimmed := strings.TrimSpace(node); trimmed != "" {
		// 		cleanedToNodes = append(cleanedToNodes, trimmed)
		// 	}
		// }

		cmd := CommandData{
			// ToNodes: The values of toNodes will be used to fill toNode field when sending below.
			JetstreamToNode:  formFields.GetFormItemByLabel("Jetstream To Node").(*tview.InputField).GetText(),
			UseDetectedShell: formFields.GetFormItemByLabel("Use Detected Shell").(*tview.Checkbox).IsChecked(),
			Method:           formFields.GetFormItemByLabel("Method").(*tview.InputField).GetText(),
			MethodArgs:       methodArgs,
			MethodTimeout:    methodTimeout,
			FromNode:         fromNode,
			ReplyMethod:      formFields.GetFormItemByLabel("Reply Method").(*tview.InputField).GetText(),
			ACKTimeout:       ackTimeout,
		}

		// Send command to each node
		for _, node := range toNodes {
			cmd.ToNode = node

			// ---------------------------

			var filePathToOpen string
			foundFile := false

			if strings.Contains(cmd.MethodArgs[2], "{{CTRL_FILE:") {
				foundFile = true

				// Example to split:
				// echo {{CTRL_FILE:/somedir/msg_file.yaml}}>ctrlfile.txt
				//
				// Split at colon. We want the part after.
				ss := strings.Split(cmd.MethodArgs[2], ":")
				// Split at "}}",so pos [0] in the result contains just the file path.
				sss := strings.Split(ss[1], "}}")
				filePathToOpen = sss[0]

			}

			if foundFile {

				fh, err := os.Open(filePathToOpen)
				if err != nil {
					fmt.Fprintf(outputView, "Error opening file: %v\n", err)
					return
				}
				defer fh.Close()

				b, err := io.ReadAll(fh)
				if err != nil {
					fmt.Fprintf(outputView, "Error reading file: %v\n", err)
					return
				}

				// Replace the {{CTRL_FILE}} with the actual content read from file.
				re := regexp.MustCompile(`(.*)({{CTRL_FILE.*}})(.*)`)
				cmd.MethodArgs[2] = re.ReplaceAllString(cmd.MethodArgs[2], `${1}`+string(b)+`${3}`)

				fmt.Fprintf(outputView, "DEBUG: Replaced {{CTRL_FILE}} with file content: %s\n", cmd.MethodArgs[2])
				// ---

			}

			// ---------------------------

			jsonData, err := json.Marshal(cmd)
			if err != nil {
				fmt.Fprintf(outputView, "Error creating command: %v\n", err)
				return
			}

			subject := fmt.Sprintf("%s.%s", strings.TrimSpace(node), cmd.Method)

			if err := nc.Publish(subject, jsonData); err != nil {
				fmt.Fprintf(outputView, "Error sending to %s: %v\n", node, err)
			} else {
				// fmt.Fprintf(outputView, "*** Command %s sent to %s, subject: %s\n", jsonData, node, subject)
			}
		}
	})

	// Layout setup - adjust the proportion between form and output
	mainFlex.AddItem(topFlex, 0, 7, true)     // Form and history section
	mainFlex.AddItem(outputView, 0, 5, false) // Output takes remaining space

	return mainFlex
}

func createNodesPage() tview.Primitive {
	// Create an empty page for now
	return tview.NewBox().SetBorder(true).SetTitle("Nodes")
}

func updateFilesList() {
	// Clear current list
	// filesList.Clear()

	// Create tui/files directory if it doesn't exist
	filesDir := "files"

	// Read all files from the directory
	files, err := os.ReadDir(filesDir)
	if err != nil {
		fmt.Fprintf(outputView, "Error reading files directory: %v\n", err)
		return
	}

	if filesList == nil {
		fmt.Printf("FILES FILE FILE: %v\n", files)
		os.Exit(1)
	}

	// Add each file to the list
	for _, file := range files {
		//fmt.Fprintf(outputView, "DEBUG: File: %s\n", file.Name())
		//if !file.IsDir() {
		filesList.AddItem(file.Name(), filepath.Join(filesDir, file.Name()), 0, nil)
		//}
	}
}

func main() {
	app = tview.NewApplication()

	// Connect to NATS
	var err error
	nc, err = nats.Connect("nats://localhost:4222")
	if err != nil {
		panic(err)
	}
	defer nc.Close()

	fromNode := "btdev1"
	// --------------------------
	// fmt.Fprintf(outputView, "DEBUG: BEFORE SUBSCRIBE\n")
	sub, err := nc.Subscribe(fromNode+".webUI", func(msg *nats.Msg) {
		var jsonMsg struct {
			Data string `json:"data"`
		}
		if err := json.Unmarshal(msg.Data, &jsonMsg); err != nil {
			app.QueueUpdateDraw(func() {
				fmt.Fprintf(outputView, "Error decoding message: %v\n", err)
			})
			return
		}

		// Decode base64 data
		decoded, err := base64.StdEncoding.DecodeString(jsonMsg.Data)
		if err != nil {
			app.QueueUpdateDraw(func() {
				fmt.Fprintf(outputView, "Error decoding base64: %v\n", err)
			})
			return
		}

		app.QueueUpdateDraw(func() {
			fmt.Fprintf(outputView, "\nReceived message on %s:\n%s\n", msg.Subject, string(decoded))
		})
	})
	// fmt.Fprintf(outputView, "DEBUG: AFTER SUBSCRIBE\n")
	if err != nil {
		panic(err)
	}
	defer func() {
		sub.Unsubscribe()
	}()
	// --------------------------

	// Create pages to manage multiple screens
	pages := tview.NewPages()

	// Create menu
	menu := tview.NewList().
		AddItem("Commands", "Send commands to nodes", 'c', func() {
			pages.SwitchToPage("commands")
		}).ShowSecondaryText(false).
		AddItem("Nodes", "View and manage nodes", 'n', func() {
			pages.SwitchToPage("nodes")
		}).ShowSecondaryText(false).
		AddItem("Quit", "Press to exit", 'q', func() {
			app.Stop()
		})
	menu.SetBorder(true).SetTitle("Menu")

	// Create layout with menu on left and pages on right
	flex := tview.NewFlex().
		AddItem(menu, 20, 1, true).
		AddItem(pages, 0, 1, false)

	// Add pages
	pages.AddPage("commands", createCommandPage(), true, true)
	pages.AddPage("nodes", createNodesPage(), true, false)

	// Global keyboard shortcuts
	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyEsc:
			// If on a page, go back to menu
			if menu.HasFocus() {
				app.Stop()
			} else {
				app.SetFocus(menu)
			}
			return nil
		case tcell.KeyTab:
			// Toggle between menu and current page
			if menu.HasFocus() {
				app.SetFocus(pages)
			} else {
				app.SetFocus(menu)
			}
			return nil
		}
		return event
	})

	// Update files list at startup
	updateFilesList()

	if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}