package cmd

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"slices"
	"time"

	"github.com/charmbracelet/crush/internal/config"
	"github.com/charmbracelet/log/v2"
	"github.com/nxadm/tail"
	"github.com/spf13/cobra"
)

func init() {
	rootCmd.AddCommand(logsCmd)
}

var logsCmd = &cobra.Command{
	Use:   "logs",
	Short: "View crush logs",
	Long:  `View the logs generated by Crush. This command allows you to see the log output for debugging and monitoring purposes.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		cwd, err := cmd.Flags().GetString("cwd")
		if err != nil {
			return fmt.Errorf("failed to get current working directory: %v", err)
		}
		log.SetLevel(log.DebugLevel)
		cfg, err := config.Load(cwd, false)
		if err != nil {
			return fmt.Errorf("failed to load configuration: %v", err)
		}
		logsFile := filepath.Join(cfg.WorkingDir(), cfg.Options.DataDirectory, "logs", "crush.log")
		_, err = os.Stat(logsFile)
		if os.IsNotExist(err) {
			log.Warn("Looks like you are not in a crush project. No logs found.")
			return nil
		}
		t, err := tail.TailFile(logsFile, tail.Config{Follow: true, ReOpen: true, Logger: tail.DiscardingLogger})
		if err != nil {
			return fmt.Errorf("failed to tail log file: %v", err)
		}

		// Print the text of each received line
		for line := range t.Lines {
			var data map[string]any
			if err := json.Unmarshal([]byte(line.Text), &data); err != nil {
				continue
			}
			msg := data["msg"]
			level := data["level"]
			otherData := []any{}
			keys := []string{}
			for k := range data {
				keys = append(keys, k)
			}
			slices.Sort(keys)
			for _, k := range keys {
				switch k {
				case "msg", "level", "time":
					continue
				case "source":
					source, ok := data[k].(map[string]any)
					if !ok {
						continue
					}
					sourceFile := fmt.Sprintf("%s:%d", source["file"], int(source["line"].(float64)))
					otherData = append(otherData, "source", sourceFile)

				default:
					otherData = append(otherData, k, data[k])
				}
			}
			log.SetTimeFunction(func(_ time.Time) time.Time {
				// parse the timestamp from the log line if available
				t, err := time.Parse(time.RFC3339, data["time"].(string))
				if err != nil {
					return time.Now() // fallback to current time if parsing fails
				}
				return t
			})
			switch level {
			case "INFO":
				log.Info(msg, otherData...)
			case "DEBUG":
				log.Debug(msg, otherData...)
			case "ERROR":
				log.Error(msg, otherData...)
			case "WARN":
				log.Warn(msg, otherData...)
			default:
				log.Info(msg, otherData...)
			}
		}
		return nil
	},
}
