package cmd

import (
	"context"
	"fmt"
	"os"
	"strings"
	"time"

	pkgoauth "github.com/giantswarm/muster/pkg/oauth"

	"github.com/giantswarm/muster/internal/api"
	"github.com/giantswarm/muster/internal/cli"

	"github.com/jedib0t/go-pretty/v6/text"
	"github.com/spf13/cobra"
)

// minNameColumnWidth is the minimum width for the server name column in status output.
// This ensures consistent alignment in the CLI output.
const minNameColumnWidth = 20

// Status-specific flags
var (
	statusServer string
)

// authStatusCmd represents the auth status command
var authStatusCmd = &cobra.Command{
	Use:   "status",
	Short: "Show authentication status",
	Long: `Show the current authentication status for all known endpoints.

This command displays which endpoints you are authenticated to, when
tokens expire, and which endpoints require authentication.

Examples:
  muster auth status                   # Show all auth status
  muster auth status --endpoint <url>  # Show status for specific endpoint
  muster auth status --server <name>   # Show status for specific MCP server`,
	RunE: runAuthStatus,
}

func init() {
	// Status-specific flags
	authStatusCmd.Flags().StringVar(&statusServer, "server", "", "MCP server name (managed by aggregator) to show status for")
}

func runAuthStatus(cmd *cobra.Command, args []string) error {
	handler, err := ensureAuthHandler()
	if err != nil {
		return err
	}

	// Get the aggregator endpoint (use --endpoint if provided, otherwise from config)
	var aggregatorEndpoint string
	if authEndpoint != "" {
		aggregatorEndpoint = authEndpoint
	} else {
		aggregatorEndpoint, err = getEndpointFromConfig()
		if err != nil {
			return err
		}
	}

	// If specific server is requested, show that server's status
	// Note: --server takes precedence over just showing the aggregator endpoint status
	if statusServer != "" {
		return showMCPServerStatus(cmd.Context(), handler, aggregatorEndpoint, statusServer)
	}

	// Show aggregator status
	authPrintln("Muster Aggregator")
	authPrint("  Endpoint:  %s\n", aggregatorEndpoint)

	// Connect via mcp-go client. The transport handles token refresh
	// automatically when the access token is expired but a valid refresh
	// token is available. This replaces the previous local-only token check
	// that could not refresh tokens.
	ctx, cancel := context.WithTimeout(cmd.Context(), DefaultStatusCheckTimeout)
	authStatus, serverErr := getAuthStatusFromAggregator(ctx, handler, aggregatorEndpoint)
	cancel()

	if serverErr == nil {
		// Connection succeeded (possibly after automatic token refresh).
		// Invalidate stale cached manager so GetStatusForEndpoint reads
		// the refreshed token from the file store.
		handler.InvalidateCache(aggregatorEndpoint)
		localStatus := handler.GetStatusForEndpoint(aggregatorEndpoint)

		if localStatus != nil && localStatus.Authenticated {
			printAuthenticatedStatus(localStatus)
		} else {
			authPrint("  Status:    %s\n", text.FgHiBlack.Sprint("No authentication required"))
		}

		if len(authStatus.Servers) > 0 {
			authPrintln("\nMCP Servers")
			printMCPServerStatuses(authStatus.Servers)
		}
		return nil
	}

	// Token is invalid and could not be refreshed -- clean up stale local token
	if pkgoauth.IsOAuthUnauthorizedError(serverErr) {
		_ = handler.Logout(aggregatorEndpoint)
		authPrint("  Status:    %s\n", text.FgYellow.Sprint("Not authenticated"))
		authPrint("             Run: muster auth login\n")
		return nil
	}

	// Network/TLS error -- fall back to local token info
	localStatus := handler.GetStatusForEndpoint(aggregatorEndpoint)
	if localStatus != nil && localStatus.Authenticated {
		printAuthenticatedStatus(localStatus)
		authPrint("  Warning:   %s\n", text.FgYellow.Sprint("Server unreachable, showing local token status"))
	} else {
		printConnectionError(serverErr, aggregatorEndpoint)
	}
	return nil
}

// printAuthenticatedStatus prints the status for a verified authenticated session.
func printAuthenticatedStatus(localStatus *api.AuthStatus) {
	authPrint("  Status:    %s\n", text.FgGreen.Sprint("Authenticated"))
	if !localStatus.ExpiresAt.IsZero() {
		authPrint("  Expires:   %s\n", formatExpiryWithDirection(localStatus.ExpiresAt))
	}
	if localStatus.HasRefreshToken {
		if !localStatus.RefreshExpiresAt.IsZero() {
			authPrint("  Session:   ~%s remaining (auto-refresh)\n", formatDuration(time.Until(localStatus.RefreshExpiresAt)))
		} else {
			authPrint("  Refresh:   %s\n", text.FgGreen.Sprint("Available"))
		}
	} else {
		authPrint("  Refresh:   %s\n", text.FgYellow.Sprint("Not available (re-auth required on expiry)"))
	}
	if localStatus.IssuerURL != "" {
		authPrint("  Issuer:    %s\n", localStatus.IssuerURL)
	}
}

// printConnectionError prints a formatted connection error message.
func printConnectionError(err error, endpoint string) {
	connErr := cli.ClassifyConnectionError(err, endpoint)
	authPrint("  Status:    %s\n", text.FgRed.Sprint("Connection failed"))
	authPrint("             %s: %s\n", connErr.Type, formatConnectionErrorReason(err))
}

// showMCPServerStatus shows the authentication status of a specific MCP server.
// The mcp-go transport handles token refresh automatically.
func showMCPServerStatus(ctx context.Context, handler api.AuthHandler, aggregatorEndpoint, serverName string) error {
	authStatus, err := getAuthStatusFromAggregator(ctx, handler, aggregatorEndpoint)
	if err != nil {
		if pkgoauth.IsOAuthUnauthorizedError(err) {
			return fmt.Errorf("not authenticated to aggregator. Run 'muster auth login' first")
		}
		return fmt.Errorf("failed to get server status: %w", err)
	}

	// Find the requested server
	for _, srv := range authStatus.Servers {
		if srv.Name == serverName {
			authPrint("\nMCP Server: %s\n", srv.Name)
			authPrint("  Status:   %s\n", formatMCPServerStatus(srv.Status))
			if ssoType := getSSOType(srv); ssoType != "" {
				authPrint("  SSO:      %s\n", ssoType)
			}
			if srv.Issuer != "" {
				authPrint("  Issuer:   %s\n", srv.Issuer)
			}
			if srv.Status == pkgoauth.SessionServerStatusAuthRequired {
				if srv.SSOAttemptFailed && (srv.TokenForwardingEnabled || srv.TokenExchangeEnabled) {
					authPrint("  Action:   SSO failed - check server configuration\n")
				} else if srv.AuthTool != "" {
					authPrint("  Action:   Run: muster auth login --server %s\n", srv.Name)
				}
			}
			if srv.Status == pkgoauth.SessionServerStatusSSOPending {
				authPrint("  Action:   Waiting for SSO to complete...\n")
			}
			return nil
		}
	}

	return fmt.Errorf("server '%s' not found. Use 'muster auth status' to see available servers", serverName)
}

// printMCPServerStatuses prints the status of all MCP servers.
func printMCPServerStatuses(servers []pkgoauth.ServerAuthStatus) {
	// Separate reachable servers from unreachable ones
	var reachableServers []pkgoauth.ServerAuthStatus
	var unreachableCount int

	for _, srv := range servers {
		if srv.Status == pkgoauth.SessionServerStatusUnreachable {
			unreachableCount++
		} else {
			reachableServers = append(reachableServers, srv)
		}
	}

	// Count servers requiring auth and find longest name for alignment
	var pendingCount int
	var ssoPendingCount int
	var maxNameLen int
	var maxSSOLen int
	for _, srv := range reachableServers {
		switch srv.Status {
		case pkgoauth.SessionServerStatusAuthRequired:
			pendingCount++
		case pkgoauth.SessionServerStatusSSOPending:
			ssoPendingCount++
		}
		if len(srv.Name) > maxNameLen {
			maxNameLen = len(srv.Name)
		}
		if ssoType := getSSOType(srv); len(ssoType) > maxSSOLen {
			maxSSOLen = len(ssoType)
		}
	}

	// Set minimum width for alignment, but allow expansion for long names
	if maxNameLen < minNameColumnWidth {
		maxNameLen = minNameColumnWidth
	}

	if pendingCount > 0 {
		fmt.Printf("  (%d pending authentication)\n", pendingCount)
	}
	if ssoPendingCount > 0 {
		fmt.Printf("  (%d SSO in progress)\n", ssoPendingCount)
	}

	for _, srv := range reachableServers {
		statusStr := formatMCPServerStatus(srv.Status)
		ssoType := getSSOType(srv)

		// Build the SSO label with consistent width padding
		var ssoLabel string
		if ssoType != "" {
			// Pad SSO type to max width for alignment, with "SSO:" prefix for context
			ssoLabel = fmt.Sprintf(" [SSO: %-*s]", maxSSOLen, ssoType)
		} else if maxSSOLen > 0 {
			// Add empty space to maintain alignment when other servers have SSO
			// Account for "SSO: " prefix (5 chars) in the padding
			ssoLabel = fmt.Sprintf("  %*s ", maxSSOLen+5, "")
		}

		if srv.Status == pkgoauth.SessionServerStatusAuthRequired && srv.SSOAttemptFailed && (srv.TokenForwardingEnabled || srv.TokenExchangeEnabled) {
			fmt.Printf("  %-*s %s%s  SSO failed - check server configuration\n", maxNameLen, srv.Name, statusStr, ssoLabel)
		} else if srv.Status == pkgoauth.SessionServerStatusAuthRequired && srv.AuthTool != "" {
			fmt.Printf("  %-*s %s%s  Run: muster auth login --server %s\n", maxNameLen, srv.Name, statusStr, ssoLabel, srv.Name)
		} else {
			fmt.Printf("  %-*s %s%s\n", maxNameLen, srv.Name, statusStr, ssoLabel)
		}
	}

	// Show summary for unreachable servers (don't show auth prompts for them)
	if unreachableCount > 0 {
		fmt.Printf("\n  (%d unreachable - not shown)\n", unreachableCount)
	}
}

// formatMCPServerStatus formats the MCP server status with colors.
func formatMCPServerStatus(status pkgoauth.SessionServerStatus) string {
	switch status {
	case pkgoauth.SessionServerStatusConnected:
		return text.FgGreen.Sprint("Connected")
	case pkgoauth.SessionServerStatusAuthRequired:
		return text.FgYellow.Sprint("Not authenticated")
	case pkgoauth.SessionServerStatusDisconnected:
		return text.FgRed.Sprint("Disconnected")
	case pkgoauth.SessionServerStatusError:
		return text.FgRed.Sprint("Error")
	case pkgoauth.SessionServerStatusSSOPending:
		return text.FgCyan.Sprint("SSO Pending")
	case pkgoauth.SessionServerStatusUnreachable:
		return text.FgHiBlack.Sprint("Unreachable")
	default:
		fmt.Fprintf(os.Stderr, "WARNING: unhandled SessionServerStatus %q\n", status)
		return text.FgHiBlack.Sprint(status)
	}
}

// ssoLabelForwarded is the label shown for SSO via token forwarding.
const ssoLabelForwarded = "Forwarded"

// ssoLabelExchanged is the label shown for SSO via token exchange.
const ssoLabelExchanged = "Exchanged"

// ssoLabelFailed is the label shown when SSO was attempted but failed.
const ssoLabelFailed = "Failed"

// getSSOType returns a human-readable SSO type for the server.
// Returns empty string if no SSO mechanism is applicable.
//
// SSO mechanisms:
//   - "Forwarded": Muster forwards its ID token to this server
//   - "Exchanged": Muster exchanges its token for one valid on the remote IdP
//   - "Failed": SSO was attempted but failed (token rejected)
func getSSOType(srv pkgoauth.ServerAuthStatus) string {
	// Show failure indicator if SSO was attempted but failed
	if srv.SSOAttemptFailed {
		return ssoLabelFailed
	}
	if srv.TokenExchangeEnabled {
		return ssoLabelExchanged
	}
	if srv.TokenForwardingEnabled {
		return ssoLabelForwarded
	}
	return ""
}

// formatConnectionErrorReason extracts a concise reason from a connection error.
// It removes verbose prefixes and presents the core issue.
func formatConnectionErrorReason(err error) string {
	if err == nil {
		return "unknown error"
	}

	errStr := err.Error()

	// Extract the most relevant part of the error message
	// TLS errors often have verbose prefixes like "Get https://...: x509: ..."
	if idx := strings.Index(errStr, "x509:"); idx != -1 {
		return strings.TrimSpace(errStr[idx:])
	}

	// For connection errors, extract the actual failure reason
	if idx := strings.Index(errStr, "connect:"); idx != -1 {
		return strings.TrimSpace(errStr[idx:])
	}

	// For dial errors, extract the core message
	if colonIdx := strings.LastIndex(errStr, ":"); strings.Contains(errStr, "dial tcp") && colonIdx != -1 {
		return strings.TrimSpace(errStr[colonIdx+1:])
	}

	// Return the error as-is if no simplification applies
	return errStr
}
