// Copyright 2025 Emiliano Spinella (eminwux)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"strings"
	"time"

	"github.com/eminwux/kukeon/cmd/config"
	createshared "github.com/eminwux/kukeon/cmd/kuke/create/shared"
	"github.com/eminwux/kukeon/internal/controller"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/yaml.v3"
)

// ExplicitFlag returns the flag's value only when the user explicitly provided it.
// This avoids viper.SetDefault side-effects leaking hardcoded defaults (e.g. "default")
// into list-operation filters, where an unset flag must mean "no filter".
func ExplicitFlag(cmd *cobra.Command, flagName, viperKey string) string {
	if cmd != nil && cmd.Flags().Changed(flagName) {
		return strings.TrimSpace(viper.GetString(viperKey))
	}
	return ""
}

// OutputFormat represents the output format type.
type OutputFormat string

const (
	OutputFormatYAML  OutputFormat = "yaml"
	OutputFormatJSON  OutputFormat = "json"
	OutputFormatTable OutputFormat = "table"
	OutputFormatWide  OutputFormat = "wide"
)

// ParseOutputFormat resolves the output format using kuke's standard
// precedence: explicit `--output`/`-o` flag > KUKE_GET_OUTPUT env (and
// any other source viper has bound to `kuke/get/output`) > table default.
// Each `kuke get <kind>` subcommand binds its own `--output` flag to the
// shared viper key at construction time, but viper only retains the last
// binding for a given key; reading the active command's flag directly
// keeps the flag working for every subcommand and lets viper handle the
// env/config fall-throughs.
func ParseOutputFormat(cmd *cobra.Command) (OutputFormat, error) {
	output := ""
	if cmd != nil && cmd.Flags().Changed("output") {
		var err error
		output, err = cmd.Flags().GetString("output")
		if err != nil {
			return OutputFormatTable, err
		}
	}
	if output == "" {
		output = viper.GetString(config.KUKE_GET_OUTPUT.ViperKey)
	}

	trimmed := strings.TrimSpace(output)
	if trimmed == "" {
		return OutputFormatTable, nil
	}
	format := OutputFormat(strings.ToLower(trimmed))
	switch format {
	case OutputFormatYAML, OutputFormatJSON, OutputFormatTable, OutputFormatWide:
		return format, nil
	default:
		return OutputFormatTable, fmt.Errorf("invalid output format: %s (supported: yaml, json, table, wide)", output)
	}
}

// PrintYAML prints the resource as YAML to cmd.OutOrStdout() so callers
// can capture output via cobra's cmd.SetOut(buf) in tests. Matches the
// shape PrintTable already takes.
func PrintYAML(cmd *cobra.Command, doc any) error {
	encoder := yaml.NewEncoder(cmd.OutOrStdout())
	encoder.SetIndent(2)
	defer func() { _ = encoder.Close() }()
	return encoder.Encode(doc)
}

// PrintJSON prints the resource as JSON to cmd.OutOrStdout() so callers
// can capture output via cobra's cmd.SetOut(buf) in tests.
func PrintJSON(cmd *cobra.Command, doc any) error {
	encoder := json.NewEncoder(cmd.OutOrStdout())
	encoder.SetIndent("", "  ")
	return encoder.Encode(doc)
}

// PrintTable prints resources in a table format.
func PrintTable(cmd *cobra.Command, headers []string, rows [][]string) {
	if len(rows) == 0 {
		cmd.Println("No resources found.")
		return
	}

	// Calculate column widths
	widths := make([]int, len(headers))
	for i, h := range headers {
		widths[i] = len(h)
	}
	for _, row := range rows {
		for i, cell := range row {
			if i < len(widths) && len(cell) > widths[i] {
				widths[i] = len(cell)
			}
		}
	}

	// Print header
	headerRow := ""
	var headerRowSb100 strings.Builder
	for i, h := range headers {
		if i > 0 {
			headerRowSb100.WriteString("  ")
		}
		headerRowSb100.WriteString(fmt.Sprintf("%-*s", widths[i], h))
	}
	headerRow += headerRowSb100.String()
	cmd.Println(headerRow)

	// Print separator
	separator := ""
	var separatorSb110 strings.Builder
	for i, w := range widths {
		if i > 0 {
			separatorSb110.WriteString("  ")
		}
		separatorSb110.WriteString(strings.Repeat("-", w))
	}
	separator += separatorSb110.String()
	cmd.Println(separator)

	// Print rows
	for _, row := range rows {
		rowStr := ""
		var rowStrSb121 strings.Builder
		for i, cell := range row {
			if i < len(widths) {
				if i > 0 {
					rowStrSb121.WriteString("  ")
				}
				rowStrSb121.WriteString(fmt.Sprintf("%-*s", widths[i], cell))
			}
		}
		rowStr += rowStrSb121.String()
		cmd.Println(rowStr)
	}
}

// RenderAge formats a CreatedAt timestamp as a kubectl-style coarse
// duration ("5d", "2h", "30s") for the AGE column shared across every
// `kuke get <kind>` table. The zero time renders as "-" so the column
// stays width-aligned for resources that have not recorded a creation
// timestamp yet. Granularity drops a unit at each step (>=1d shows days
// only, >=1h shows hours only, etc.); sub-second values render as "0s".
// `now` is injected so tests can pin the reference time.
func RenderAge(t time.Time, now time.Time) string {
	if t.IsZero() {
		return "-"
	}
	d := now.Sub(t)
	if d < 0 {
		d = 0
	}
	const day = 24 * time.Hour
	switch {
	case d >= day:
		return fmt.Sprintf("%dd", int(d/day))
	case d >= time.Hour:
		return fmt.Sprintf("%dh", int(d/time.Hour))
	case d >= time.Minute:
		return fmt.Sprintf("%dm", int(d/time.Minute))
	default:
		return fmt.Sprintf("%ds", int(d/time.Second))
	}
}

// ControllerFromCmd reuses the controller helper from create/shared.
func ControllerFromCmd(cmd *cobra.Command) (*controller.Exec, error) {
	return createshared.ControllerFromCmd(cmd)
}

// LoggerFromCmd reuses the logger helper from create/shared.
func LoggerFromCmd(cmd *cobra.Command) (*slog.Logger, error) {
	return createshared.LoggerFromCmd(cmd)
}
