package cmd

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/spf13/cobra"

	"github.com/tidbcloud/AgentTeam/desktop/internal/operator"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

var authLoginOpts struct {
	serverURL string
	apiKey    string
}

var authStatusOpts struct {
	format string
}

var authLogoutOpts struct {
	all bool
}

var authCmd = &cobra.Command{
	Use:   "auth",
	Short: "Manage AgentTeam server auth",
	Long: `Manage AgentTeam server authentication entries.

Examples:
  agentteam auth login --api-key <machine-api-key>
  agentteam auth login --server-url https://agentteam.example.com --api-key <machine-api-key>
  agentteam auth status
  agentteam auth logout srv_8Kp3mX9aQ2bL`,
}

var authLoginCmd = &cobra.Command{
	Use:          "login --api-key <machine-api-key>",
	Short:        "Log in this machine to a AgentTeam server",
	SilenceUsage: true,
	RunE:         runAuthLogin,
}

var authStatusCmd = &cobra.Command{
	Use:   "status",
	Short: "Show AgentTeam auth status",
	RunE:  runAuthStatus,
}

var authLogoutCmd = &cobra.Command{
	Use:   "logout <id>",
	Short: "Remove AgentTeam auth",
	Args: func(_ *cobra.Command, args []string) error {
		if authLogoutOpts.all {
			if len(args) != 0 {
				return fmt.Errorf("logout --all does not accept an ID")
			}
			return nil
		}
		if len(args) != 1 {
			return fmt.Errorf("logout requires an auth ID or --all")
		}
		return nil
	},
	RunE: runAuthLogout,
}

type agentteamAuthSaveResult struct {
	Server  profile.AgentTeamServer
	Created bool
}

type agentteamAuthStatusInfo struct {
	ID          string `json:"id" yaml:"id"`
	ServerURL   string `json:"serverUrl" yaml:"serverUrl"`
	WorkspaceID string `json:"workspaceId" yaml:"workspaceId"`
	MachineID   string `json:"machineId" yaml:"machineId"`
	Status      string `json:"status" yaml:"status"`
	Enabled     bool   `json:"enabled" yaml:"enabled"`
}

var resolveAgentTeamMachineAPIKey = resolveAgentTeamMachineAPIKeyImpl

func init() {
	authLoginCmd.Flags().StringVar(&authLoginOpts.serverURL, "server-url", defaultAgentTeamServerURL, "AgentTeam server URL")
	authLoginCmd.Flags().StringVar(&authLoginOpts.apiKey, "api-key", "", "machine API key")
	authStatusCmd.Flags().StringVarP(&authStatusOpts.format, "format", "f", "table", "output format (table|json|yaml)")
	authLogoutCmd.Flags().BoolVar(&authLogoutOpts.all, "all", false, "remove all AgentTeam auth entries")

	authCmd.AddCommand(authLoginCmd)
	authCmd.AddCommand(authStatusCmd)
	authCmd.AddCommand(authLogoutCmd)
}

func runAuthLogin(_ *cobra.Command, _ []string) error {
	resolved, err := resolveManualAgentTeamAuth(authLoginOpts.serverURL, authLoginOpts.apiKey)
	if err != nil {
		return fmt.Errorf("AgentTeam auth failed: %w", err)
	}

	op, err := getOperator()
	if err != nil {
		return err
	}
	saveResult, err := upsertAgentTeamAuth(op.Servers(), *resolved)
	if err != nil {
		return err
	}
	printAgentTeamAuthResult(saveResult)

	return nil
}

func runAuthStatus(_ *cobra.Command, _ []string) error {
	op, err := getOperator()
	if err != nil {
		return err
	}
	servers, err := op.Servers().All()
	if err != nil {
		return err
	}
	infos := make([]agentteamAuthStatusInfo, 0, len(servers))
	for _, server := range servers {
		infos = append(infos, agentteamAuthInfo(server))
	}
	if len(infos) == 0 && strings.EqualFold(authStatusOpts.format, "table") {
		fmt.Println("No AgentTeam auth found. Run: agentteam auth login --api-key <key>")
		return nil
	}

	switch strings.ToLower(authStatusOpts.format) {
	case "table":
		return printAgentTeamAuthStatusTable(infos)
	case "json":
		return printJSON(infos, false)
	case "yaml":
		return printYAML(infos)
	default:
		return fmt.Errorf("unsupported format: %s (valid: table, json, yaml)", authStatusOpts.format)
	}
}

func runAuthLogout(_ *cobra.Command, args []string) error {
	op, err := getOperator()
	if err != nil {
		return err
	}
	if authLogoutOpts.all {
		servers, err := op.Servers().All()
		if err != nil {
			return err
		}
		for _, server := range servers {
			if err := op.Servers().Delete(server.ID); err != nil {
				return err
			}
		}
		fmt.Println("Logged out from all AgentTeam auth.")
		return nil
	}

	id := strings.TrimSpace(args[0])
	server, ok, err := op.Servers().Get(id)
	if err != nil {
		return err
	}
	if !ok {
		return fmt.Errorf("AgentTeam auth %q not found", id)
	}
	if err := op.Servers().Delete(id); err != nil {
		return err
	}

	fmt.Println("Logged out from AgentTeam.")
	fmt.Println()
	fmt.Printf("ID:        %s\n", server.ID)
	fmt.Printf("Server:    %s\n", agentteamServerURL(server))
	fmt.Printf("Workspace: %s\n", agentteamServerWorkspaceID(server))
	return nil
}

func resolveManualAgentTeamAuth(serverURL string, apiKey string) (*agentteamAuthResolution, error) {
	normalizedServerURL := normalizeAgentTeamServerURL(serverURL)
	if normalizedServerURL == "" {
		normalizedServerURL = defaultAgentTeamServerURL
	}
	if strings.TrimSpace(apiKey) == "" {
		return nil, fmt.Errorf("--api-key is required")
	}
	return resolveAgentTeamMachineAPIKey(normalizedServerURL, apiKey)
}

func resolveAgentTeamMachineAPIKeyImpl(serverURL string, apiKey string) (*agentteamAuthResolution, error) {
	payload, err := json.Marshal(map[string]string{
		"serverUrl": normalizeAgentTeamServerURL(serverURL),
		"apiKey":    strings.TrimSpace(apiKey),
	})
	if err != nil {
		return nil, fmt.Errorf("marshal request: %w", err)
	}

	endpoint := strings.TrimRight(serverURL, "/") + "/api/v1/machine-auth-resolutions"
	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Post(endpoint, "application/json", bytes.NewReader(payload))
	if err != nil {
		return nil, fmt.Errorf("request auth resolve endpoint: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read auth resolve response: %w", err)
	}
	if resp.StatusCode == http.StatusUnauthorized {
		return nil, fmt.Errorf("invalid machine API key")
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}

	var result agentteamAuthResolution
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, fmt.Errorf("decode auth resolve response: %w", err)
	}
	return normalizeAgentTeamAuthResolution(serverURL, apiKey, &result)
}

func normalizeAgentTeamAuthResolution(serverURL string, apiKey string, result *agentteamAuthResolution) (*agentteamAuthResolution, error) {
	if result == nil {
		return nil, fmt.Errorf("empty auth response")
	}
	if result.ServerURL = normalizeAgentTeamServerURL(result.ServerURL); result.ServerURL == "" {
		result.ServerURL = normalizeAgentTeamServerURL(serverURL)
	}
	if result.APIKey = strings.TrimSpace(result.APIKey); result.APIKey == "" {
		result.APIKey = strings.TrimSpace(apiKey)
	}
	result.WorkspaceID = strings.TrimSpace(result.WorkspaceID)
	result.MachineID = strings.TrimSpace(result.MachineID)
	if result.ServerURL == "" {
		return nil, fmt.Errorf("server URL is required")
	}
	if result.APIKey == "" {
		return nil, fmt.Errorf("api key is required")
	}
	if result.WorkspaceID == "" {
		return nil, fmt.Errorf("server did not return workspaceId")
	}
	if result.MachineID == "" {
		return nil, fmt.Errorf("server did not return machineId")
	}
	return result, nil
}

func upsertAgentTeamAuth(servers interface {
	operator.ServerReader
	operator.ServerWriter
	operator.ServerEnabler
}, auth agentteamAuthResolution) (agentteamAuthSaveResult, error) {
	serverURL := normalizeAgentTeamServerURL(auth.ServerURL)
	workspaceID := strings.TrimSpace(auth.WorkspaceID)
	apiKey := strings.TrimSpace(auth.APIKey)
	machineID := strings.TrimSpace(auth.MachineID)
	if serverURL == "" || workspaceID == "" || apiKey == "" || machineID == "" {
		return agentteamAuthSaveResult{}, fmt.Errorf("resolved AgentTeam auth must include serverUrl, workspaceId, machineId, and apiKey")
	}

	existingServers, err := servers.All()
	if err != nil {
		return agentteamAuthSaveResult{}, err
	}

	var matches []profile.AgentTeamServer
	for _, server := range existingServers {
		if agentteamServerURL(server) != serverURL {
			continue
		}
		existingWorkspaceID := agentteamServerWorkspaceID(server)
		if existingWorkspaceID == workspaceID || (existingWorkspaceID == "" && agentteamServerAPIKey(server) == apiKey) {
			matches = append(matches, server)
		}
	}

	if len(matches) > 0 {
		primary := choosePrimaryAgentTeamServer(matches)
		persisted, err := persistAgentTeamAuth(servers, primary, serverURL, apiKey, workspaceID, machineID)
		if err != nil {
			return agentteamAuthSaveResult{}, err
		}
		if err := disableDuplicateAgentTeamServers(servers, matches, persisted.ID); err != nil {
			return agentteamAuthSaveResult{}, err
		}
		return agentteamAuthSaveResult{Server: persisted, Created: false}, nil
	}

	server := profile.AgentTeamServer{
		Enabled:     true,
		ServerURL:   serverURL,
		WorkspaceID: workspaceID,
		MachineID:   machineID,
		APIKey:      apiKey,
		Config:      mapToRawMessage(connectedAgentTeamAuthConfig(serverURL, apiKey, workspaceID, machineID)),
	}
	persisted, err := servers.Add(&server)
	if err != nil {
		return agentteamAuthSaveResult{}, err
	}
	return agentteamAuthSaveResult{Server: persisted, Created: true}, nil
}

func persistAgentTeamAuth(servers operator.ServerWriter, server profile.AgentTeamServer, serverURL string, apiKey string, workspaceID string, machineID string) (profile.AgentTeamServer, error) {
	server.Enabled = true
	server.ServerURL = serverURL
	server.WorkspaceID = workspaceID
	server.MachineID = machineID
	server.APIKey = apiKey
	server.AuthToken = ""
	server.ConnectionStatus = ""
	server.Config = mapToRawMessage(connectedAgentTeamAuthConfig(serverURL, apiKey, workspaceID, machineID))
	return servers.Update(server)
}

func printAgentTeamAuthResult(result agentteamAuthSaveResult) {
	if result.Created {
		fmt.Println("Logged in to AgentTeam.")
	} else {
		fmt.Println("Updated AgentTeam auth.")
	}
	fmt.Println()
	fmt.Printf("ID:        %s\n", result.Server.ID)
	fmt.Printf("Server:    %s\n", agentteamServerURL(result.Server))
	fmt.Printf("Workspace: %s\n", agentteamServerWorkspaceID(result.Server))
	fmt.Printf("Machine:   %s\n", agentteamServerMachineID(result.Server))
	fmt.Printf("Status:    %s\n", agentteamAuthStatus(result.Server))
}

func agentteamAuthInfo(server profile.AgentTeamServer) agentteamAuthStatusInfo {
	return agentteamAuthStatusInfo{
		ID:          server.ID,
		ServerURL:   agentteamServerURL(server),
		WorkspaceID: agentteamServerWorkspaceID(server),
		MachineID:   agentteamServerMachineID(server),
		Status:      agentteamAuthStatus(server),
		Enabled:     server.Enabled,
	}
}

func agentteamAuthStatus(server profile.AgentTeamServer) string {
	if status := agentteamServerConnectionStatus(server); status != "" {
		return status
	}
	if agentteamServerAPIKey(server) == "" {
		return "missing_api_key"
	}
	return "ready"
}

func printAgentTeamAuthStatusTable(infos []agentteamAuthStatusInfo) error {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "ID\tSERVER URL\tWORKSPACE\tMACHINE\tSTATUS\tENABLED")
	for _, info := range infos {
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%v\n", info.ID, info.ServerURL, info.WorkspaceID, info.MachineID, info.Status, info.Enabled)
	}
	return w.Flush()
}

func choosePrimaryAgentTeamServer(matches []profile.AgentTeamServer) profile.AgentTeamServer {
	for _, server := range matches {
		if server.Enabled {
			return server
		}
	}
	return matches[0]
}

func disableDuplicateAgentTeamServers(servers operator.ServerEnabler, matches []profile.AgentTeamServer, keepID string) error {
	for _, server := range matches {
		if server.ID == keepID || !server.Enabled {
			continue
		}
		if err := servers.SetEnabled(server.ID, false); err != nil {
			return fmt.Errorf("disable duplicate agentteam server %q: %w", server.ID, err)
		}
	}
	return nil
}

func normalizeAgentTeamServerURL(value string) string {
	value = strings.TrimSpace(value)
	return strings.TrimRight(value, "/")
}

func agentteamServerURL(server profile.AgentTeamServer) string {
	if value := strings.TrimSpace(server.ServerURL); value != "" {
		return normalizeAgentTeamServerURL(value)
	}
	cfg := configToMap(server.Config)
	value, ok := cfg["server_url"]
	if !ok || value == nil {
		return ""
	}
	return normalizeAgentTeamServerURL(fmt.Sprint(value))
}

func agentteamServerAPIKey(server profile.AgentTeamServer) string {
	if value := strings.TrimSpace(server.APIKey); value != "" {
		return value
	}
	cfg := configToMap(server.Config)
	value, ok := cfg["api_key"]
	if !ok || value == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprint(value))
}

func agentteamServerWorkspaceID(server profile.AgentTeamServer) string {
	if value := strings.TrimSpace(server.WorkspaceID); value != "" {
		return value
	}
	cfg := configToMap(server.Config)
	value, ok := cfg["workspace_id"]
	if !ok || value == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprint(value))
}

func agentteamServerMachineID(server profile.AgentTeamServer) string {
	if value := strings.TrimSpace(server.MachineID); value != "" {
		return value
	}
	cfg := configToMap(server.Config)
	value, ok := cfg["machine_id"]
	if !ok || value == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprint(value))
}

func agentteamServerConnectionStatus(server profile.AgentTeamServer) string {
	if value := strings.TrimSpace(server.ConnectionStatus); value != "" {
		return value
	}
	cfg := configToMap(server.Config)
	value, ok := cfg["connection_status"]
	if !ok || value == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprint(value))
}
