package main

import (
	"bufio"
	"fmt"
	"log"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/pelletier/go-toml"
	"github.com/prometheus/common/expfmt"
	"github.com/rivo/tview"
	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/mem"
	"github.com/shirou/gopsutil/v3/process"
)

var prometheusURL string
var configFilePath string // Pfad der gefundenen Konfiguration
var logFilePath string    // Pfad der Logdatei aus der Konfiguration

func init() {
	configPaths := []string{
		"/etc/hmac-file-server/config.toml",
		"../config.toml",
		"./config.toml",
	}

	var config *toml.Tree
	var err error

	// Lade die config.toml aus den definierten Pfaden
	for _, path := range configPaths {
		config, err = toml.LoadFile(path)
		if err == nil {
			configFilePath = path
			log.Printf("Using config file: %s", configFilePath)
			break
		}
	}

	if err != nil {
		log.Fatalf("Error loading config file: %v", err)
	}

	// Metricsport auslesen
	portValue := config.Get("server.metricsport")
	if portValue == nil {
		log.Println("Warning: 'server.metricsport' is missing in the configuration, using default port 9090")
		portValue = int64(9090)
	}

	var port int64
	switch v := portValue.(type) {
	case int64:
		port = v
	case string:
		parsedPort, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			log.Fatalf("Error parsing 'server.metricsport' as int64: %v", err)
		}
		port = parsedPort
	default:
		log.Fatalf("Error: 'server.metricsport' is not of type int64 or string, got %T", v)
	}

	prometheusURL = fmt.Sprintf("http://localhost:%d/metrics", port)

	// Log-Datei auslesen über server.logfile
	logFileValue := config.Get("server.logfile")
	if logFileValue == nil {
		log.Println("Warning: 'server.logfile' is missing, using default '/var/log/hmac-file-server.log'")
		logFilePath = "/var/log/hmac-file-server.log"
	} else {
		lf, ok := logFileValue.(string)
		if !ok {
			log.Fatalf("Error: 'server.logfile' is not of type string, got %T", logFileValue)
		}
		logFilePath = lf
	}
}

// Thresholds for color coding
const (
	HighUsage   = 80.0
	MediumUsage = 50.0
)

// ProcessInfo holds information about a process
type ProcessInfo struct {
	PID         int32
	Name        string
	CPUPercent  float64
	MemPercent  float32
	CommandLine string
}

// Function to fetch and parse Prometheus metrics
func fetchMetrics() (map[string]float64, error) {
	resp, err := http.Get(prometheusURL)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch metrics: %w", err)
	}
	defer resp.Body.Close()

	parser := &expfmt.TextParser{}
	metricFamilies, err := parser.TextToMetricFamilies(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to parse metrics: %w", err)
	}

	metrics := make(map[string]float64)
	for name, mf := range metricFamilies {
		// Filter the metrics you're interested in
		if strings.HasPrefix(name, "hmac_file_server_") ||
			name == "memory_usage_bytes" ||
			name == "cpu_usage_percent" ||
			name == "active_connections_total" ||
			name == "goroutines_count" {

			for _, m := range mf.GetMetric() {
				var value float64
				if m.GetGauge() != nil {
					value = m.GetGauge().GetValue()
				} else if m.GetCounter() != nil {
					value = m.GetCounter().GetValue()
				} else if m.GetUntyped() != nil {
					value = m.GetUntyped().GetValue()
				} else {
					// If the metric type is not handled, skip it
					continue
				}

				// Handle metrics with labels
				if len(m.GetLabel()) > 0 {
					labels := make([]string, 0)
					for _, label := range m.GetLabel() {
						labels = append(labels, fmt.Sprintf("%s=\"%s\"", label.GetName(), label.GetValue()))
					}
					metricKey := fmt.Sprintf("%s{%s}", name, strings.Join(labels, ","))
					metrics[metricKey] = value
				} else {
					metrics[name] = value
				}
			}
		}
	}

	return metrics, nil
}

// Function to fetch system data
func fetchSystemData() (float64, float64, int, error) {
	v, err := mem.VirtualMemory()
	if err != nil {
		return 0, 0, 0, fmt.Errorf("failed to fetch memory data: %w", err)
	}

	c, err := cpu.Percent(0, false)
	if err != nil {
		return 0, 0, 0, fmt.Errorf("failed to fetch CPU data: %w", err)
	}

	cores, err := cpu.Counts(true)
	if err != nil {
		return 0, 0, 0, fmt.Errorf("failed to fetch CPU cores: %w", err)
	}

	cpuUsage := 0.0
	if len(c) > 0 {
		cpuUsage = c[0]
	}

	return v.UsedPercent, cpuUsage, cores, nil
}

// Function to fetch process list
func fetchProcessList() ([]ProcessInfo, error) {
	processes, err := process.Processes()
	if err != nil {
		return nil, fmt.Errorf("failed to fetch processes: %w", err)
	}

	var processList []ProcessInfo

	for _, p := range processes {
		cpuPercent, err := p.CPUPercent()
		if err != nil {
			continue
		}

		memPercent, err := p.MemoryPercent()
		if err != nil {
			continue
		}

		name, err := p.Name()
		if err != nil {
			continue
		}

		cmdline, err := p.Cmdline()
		if err != nil {
			cmdline = ""
		}

		processList = append(processList, ProcessInfo{
			PID:         p.Pid,
			Name:        name,
			CPUPercent:  cpuPercent,
			MemPercent:  memPercent,
			CommandLine: cmdline,
		})
	}

	return processList, nil
}

// Function to fetch detailed information about hmac-file-server
func fetchHmacFileServerInfo() (*ProcessInfo, error) {
	processes, err := process.Processes()
	if err != nil {
		return nil, fmt.Errorf("failed to fetch processes: %w", err)
	}

	for _, p := range processes {
		name, err := p.Name()
		if err != nil {
			continue
		}

		if name == "hmac-file-server" {
			cpuPercent, err := p.CPUPercent()
			if err != nil {
				cpuPercent = 0.0
			}

			memPercent, err := p.MemoryPercent()
			if err != nil {
				memPercent = 0.0
			}

			cmdline, err := p.Cmdline()
			if err != nil {
				cmdline = ""
			}

			return &ProcessInfo{
				PID:         p.Pid,
				Name:        name,
				CPUPercent:  cpuPercent,
				MemPercent:  memPercent,
				CommandLine: cmdline,
			}, nil
		}
	}

	return nil, fmt.Errorf("hmac-file-server process not found")
}

// Function to update the UI with the latest data
func updateUI(app *tview.Application, pages *tview.Pages, sysPage, hmacPage tview.Primitive) {
	ticker := time.NewTicker(2 * time.Second)
	defer ticker.Stop()

	for range ticker.C {
		// Fetch data for both views
		memUsage, cpuUsage, cores, err := fetchSystemData()
		if err != nil {
			log.Printf("Error fetching system data: %v\n", err)
			continue
		}

		metrics, err := fetchMetrics()
		if err != nil {
			log.Printf("Error fetching metrics: %v\n", err)
			continue
		}

		processes, err := fetchProcessList()
		if err != nil {
			log.Printf("Error fetching process list: %v\n", err)
			continue
		}

		hmacInfo, err := fetchHmacFileServerInfo()
		if err != nil {
			log.Printf("Error fetching hmac-file-server info: %v\n", err)
		}

		// Update the UI
		app.QueueUpdateDraw(func() {
			// Update system page
			if currentPage, _ := pages.GetFrontPage(); currentPage == "system" {
				sysFlex := sysPage.(*tview.Flex)

				// Update system data table
				sysTable := sysFlex.GetItem(0).(*tview.Table)
				updateSystemTable(sysTable, memUsage, cpuUsage, cores)

				// Update metrics table
				metricsTable := sysFlex.GetItem(1).(*tview.Table)
				updateMetricsTable(metricsTable, metrics)

				// Update process table
				processTable := sysFlex.GetItem(2).(*tview.Table)
				updateProcessTable(processTable, processes)
			}

			// Update hmac-file-server page
			if currentPage, _ := pages.GetFrontPage(); currentPage == "hmac" && hmacInfo != nil {
				hmacFlex := hmacPage.(*tview.Flex)
				hmacTable := hmacFlex.GetItem(0).(*tview.Table)
				updateHmacTable(hmacTable, hmacInfo, metrics)
			}
		})
	}
}

// Helper function to update system data table
func updateSystemTable(sysTable *tview.Table, memUsage, cpuUsage float64, cores int) {
	sysTable.Clear()
	sysTable.SetCell(0, 0, tview.NewTableCell("Metric").SetAttributes(tcell.AttrBold))
	sysTable.SetCell(0, 1, tview.NewTableCell("Value").SetAttributes(tcell.AttrBold))

	// CPU Usage Row
	cpuUsageCell := tview.NewTableCell(fmt.Sprintf("%.2f%%", cpuUsage))
	if cpuUsage > HighUsage {
		cpuUsageCell.SetTextColor(tcell.ColorRed)
	} else if cpuUsage > MediumUsage {
		cpuUsageCell.SetTextColor(tcell.ColorYellow)
	} else {
		cpuUsageCell.SetTextColor(tcell.ColorGreen)
	}
	sysTable.SetCell(1, 0, tview.NewTableCell("CPU Usage"))
	sysTable.SetCell(1, 1, cpuUsageCell)

	// Memory Usage Row
	memUsageCell := tview.NewTableCell(fmt.Sprintf("%.2f%%", memUsage))
	if memUsage > HighUsage {
		memUsageCell.SetTextColor(tcell.ColorRed)
	} else if memUsage > MediumUsage {
		memUsageCell.SetTextColor(tcell.ColorYellow)
	} else {
		memUsageCell.SetTextColor(tcell.ColorGreen)
	}
	sysTable.SetCell(2, 0, tview.NewTableCell("Memory Usage"))
	sysTable.SetCell(2, 1, memUsageCell)

	// CPU Cores Row
	sysTable.SetCell(3, 0, tview.NewTableCell("CPU Cores"))
	sysTable.SetCell(3, 1, tview.NewTableCell(fmt.Sprintf("%d", cores)))
}

// Helper function to update metrics table
func updateMetricsTable(metricsTable *tview.Table, metrics map[string]float64) {
	metricsTable.Clear()
	metricsTable.SetCell(0, 0, tview.NewTableCell("Metric").SetAttributes(tcell.AttrBold))
	metricsTable.SetCell(0, 1, tview.NewTableCell("Value").SetAttributes(tcell.AttrBold))

	row := 1
	for key, value := range metrics {
		metricsTable.SetCell(row, 0, tview.NewTableCell(key))
		metricsTable.SetCell(row, 1, tview.NewTableCell(fmt.Sprintf("%.2f", value)))
		row++
	}
}

// Helper function to update process table
func updateProcessTable(processTable *tview.Table, processes []ProcessInfo) {
	processTable.Clear()
	processTable.SetCell(0, 0, tview.NewTableCell("PID").SetAttributes(tcell.AttrBold))
	processTable.SetCell(0, 1, tview.NewTableCell("Name").SetAttributes(tcell.AttrBold))
	processTable.SetCell(0, 2, tview.NewTableCell("CPU%").SetAttributes(tcell.AttrBold))
	processTable.SetCell(0, 3, tview.NewTableCell("Mem%").SetAttributes(tcell.AttrBold))
	processTable.SetCell(0, 4, tview.NewTableCell("Command").SetAttributes(tcell.AttrBold))

	// Sort processes by CPU usage
	sort.Slice(processes, func(i, j int) bool {
		return processes[i].CPUPercent > processes[j].CPUPercent
	})

	// Limit to top 20 processes
	maxRows := 20
	if len(processes) < maxRows {
		maxRows = len(processes)
	}

	for i := 0; i < maxRows; i++ {
		p := processes[i]
		processTable.SetCell(i+1, 0, tview.NewTableCell(fmt.Sprintf("%d", p.PID)))
		processTable.SetCell(i+1, 1, tview.NewTableCell(p.Name))
		processTable.SetCell(i+1, 2, tview.NewTableCell(fmt.Sprintf("%.2f", p.CPUPercent)))
		processTable.SetCell(i+1, 3, tview.NewTableCell(fmt.Sprintf("%.2f", p.MemPercent)))
		processTable.SetCell(i+1, 4, tview.NewTableCell(p.CommandLine))
	}
}

// Helper function to update hmac-table
func updateHmacTable(hmacTable *tview.Table, hmacInfo *ProcessInfo, metrics map[string]float64) {
	hmacTable.Clear()
	hmacTable.SetCell(0, 0, tview.NewTableCell("Property").SetAttributes(tcell.AttrBold))
	hmacTable.SetCell(0, 1, tview.NewTableCell("Value").SetAttributes(tcell.AttrBold))

	// Process information
	hmacTable.SetCell(1, 0, tview.NewTableCell("PID"))
	hmacTable.SetCell(1, 1, tview.NewTableCell(fmt.Sprintf("%d", hmacInfo.PID)))

	hmacTable.SetCell(2, 0, tview.NewTableCell("CPU%"))
	hmacTable.SetCell(2, 1, tview.NewTableCell(fmt.Sprintf("%.2f", hmacInfo.CPUPercent)))

	hmacTable.SetCell(3, 0, tview.NewTableCell("Mem%"))
	hmacTable.SetCell(3, 1, tview.NewTableCell(fmt.Sprintf("%.2f", hmacInfo.MemPercent)))

	hmacTable.SetCell(4, 0, tview.NewTableCell("Command"))
	hmacTable.SetCell(4, 1, tview.NewTableCell(hmacInfo.CommandLine))

	// Metrics related to hmac-file-server
	row := 6
	hmacTable.SetCell(row, 0, tview.NewTableCell("Metric").SetAttributes(tcell.AttrBold))
	hmacTable.SetCell(row, 1, tview.NewTableCell("Value").SetAttributes(tcell.AttrBold))
	row++

	for key, value := range metrics {
		if strings.Contains(key, "hmac_file_server_") {
			hmacTable.SetCell(row, 0, tview.NewTableCell(key))
			hmacTable.SetCell(row, 1, tview.NewTableCell(fmt.Sprintf("%.2f", value)))
			row++
		}
	}
}

func createSystemPage() tview.Primitive {
	// Create system data table
	sysTable := tview.NewTable().SetBorders(false)
	sysTable.SetTitle(" [::b]System Data ").SetBorder(true)

	// Create Prometheus metrics table
	metricsTable := tview.NewTable().SetBorders(false)
	metricsTable.SetTitle(" [::b]Prometheus Metrics ").SetBorder(true)

	// Create process list table
	processTable := tview.NewTable().SetBorders(false)
	processTable.SetTitle(" [::b]Process List ").SetBorder(true)

	// Create a flex layout to hold the tables
	sysFlex := tview.NewFlex().
		SetDirection(tview.FlexRow).
		AddItem(sysTable, 7, 0, false).
		AddItem(metricsTable, 0, 1, false).
		AddItem(processTable, 0, 2, false)

	return sysFlex
}

func createHmacPage() tview.Primitive {
	hmacTable := tview.NewTable().SetBorders(false)
	hmacTable.SetTitle(" [::b]hmac-file-server Details ").SetBorder(true)

	hmacFlex := tview.NewFlex().
		SetDirection(tview.FlexRow).
		AddItem(hmacTable, 0, 1, false)

	return hmacFlex
}

func createLogsPage(logFilePath string) tview.Primitive {
    logsTextView := tview.NewTextView().
        SetDynamicColors(true).
        SetRegions(true).
        SetWordWrap(true)
    logsTextView.SetTitle(" [::b]Logs ").SetBorder(true)

    const numLines = 100 // Number of lines to read from the end of the log file

    // Read logs periodically
    go func() {
        for {
            content, err := readLastNLines(logFilePath, numLines)
            if err != nil {
                logsTextView.SetText(fmt.Sprintf("[red]Error reading log file: %v[white]", err))
            } else {
                // Process the log content to add colors
                lines := strings.Split(content, "\n")
                var coloredLines []string
                for _, line := range lines {
                    if strings.Contains(line, "level=info") {
                        coloredLines = append(coloredLines, "[green]"+line+"[white]")
                    } else if strings.Contains(line, "level=warn") {
                        coloredLines = append(coloredLines, "[yellow]"+line+"[white]")
                    } else if strings.Contains(line, "level=error") {
                        coloredLines = append(coloredLines, "[red]"+line+"[white]")
                    } else {
                        // Default color
                        coloredLines = append(coloredLines, line)
                    }
                }
                logsTextView.SetText(strings.Join(coloredLines, "\n"))
            }
            time.Sleep(2 * time.Second) // Refresh interval for logs
        }
    }()

    return logsTextView
}

func readLastNLines(filePath string, n int) (string, error) {
    file, err := os.Open(filePath)
    if err != nil {
        return "", err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
        if len(lines) > n {
            lines = lines[1:]
        }
    }

    if err := scanner.Err(); err != nil {
        return "", err
    }

    return strings.Join(lines, "\n"), nil
}

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

	// Create pages
	pages := tview.NewPages()

	// System page
	sysPage := createSystemPage()
	pages.AddPage("system", sysPage, true, true)

	// hmac-file-server page
	hmacPage := createHmacPage()
	pages.AddPage("hmac", hmacPage, true, false)

	// Logs page mit dem gelesenen logFilePath
	logsPage := createLogsPage(logFilePath)
	pages.AddPage("logs", logsPage, true, false)

	// Add key binding to switch views
	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		if event.Key() == tcell.KeyRune {
			switch event.Rune() {
			case 'q', 'Q':
				app.Stop()
				return nil
			case 's', 'S':
				// Switch to system page
				pages.SwitchToPage("system")
			case 'h', 'H':
				// Switch to hmac-file-server page
				pages.SwitchToPage("hmac")
			case 'l', 'L':
				// Switch to logs page
				pages.SwitchToPage("logs")
			}
		}
		return event
	})

	// Start the UI update loop in a separate goroutine
	go updateUI(app, pages, sysPage, hmacPage)

	// Set the root and run the application
	if err := app.SetRoot(pages, true).EnableMouse(true).Run(); err != nil {
		log.Fatalf("Error running application: %v", err)
	}
}