package auth

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// EnhancedCredentialStore manages multiple credentials per gateway
type EnhancedCredentialStore struct {
	Gateways map[string]*GatewayCredentials `json:"gateways"`
	Version  string                         `json:"version"`
}

// GatewayCredentials holds multiple credentials for a single gateway
type GatewayCredentials struct {
	Credentials   []*Credentials `json:"credentials"`
	DefaultIndex  int            `json:"default_index"`
	LastUsedIndex int            `json:"last_used_index"`
}

// AuthChoice represents user's choice during authentication
type AuthChoice int

const (
	AuthChoiceUseCredential AuthChoice = iota
	AuthChoiceAddCredential
	AuthChoiceLogout
	AuthChoiceExit
)

// LoadEnhancedCredentials loads the enhanced credential store, with migration support from legacy v2.0 format
func LoadEnhancedCredentials() (*EnhancedCredentialStore, error) {
	credPath, err := GetCredentialsPath()
	if err != nil {
		return nil, err
	}

	// If file doesn't exist, return empty store
	if _, err := os.Stat(credPath); os.IsNotExist(err) {
		return &EnhancedCredentialStore{
			Gateways: make(map[string]*GatewayCredentials),
			Version:  "2.0",
		}, nil
	}

	data, err := os.ReadFile(credPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read credentials file: %w", err)
	}

	// First, try to parse as the proper enhanced store
	var enhancedStore EnhancedCredentialStore
	if err := json.Unmarshal(data, &enhancedStore); err == nil && enhancedStore.Version == "2.0" {
		// Check if it's already in the enhanced format (has credentials arrays)
		hasCredentialsArrays := true
		for _, gwCreds := range enhancedStore.Gateways {
			if gwCreds == nil || gwCreds.Credentials == nil {
				hasCredentialsArrays = false
				break
			}
		}

		if hasCredentialsArrays {
			// Already in enhanced format, just sanitize indices
			if enhancedStore.Gateways == nil {
				enhancedStore.Gateways = make(map[string]*GatewayCredentials)
			}
			for _, gw := range enhancedStore.Gateways {
				if len(gw.Credentials) == 0 {
					gw.DefaultIndex = 0
					gw.LastUsedIndex = 0
					continue
				}
				if gw.DefaultIndex < 0 || gw.DefaultIndex >= len(gw.Credentials) {
					gw.DefaultIndex = 0
				}
				if gw.LastUsedIndex < 0 || gw.LastUsedIndex >= len(gw.Credentials) {
					gw.LastUsedIndex = gw.DefaultIndex
				}
			}
			return &enhancedStore, nil
		}
	}

	// Parse as legacy format (single credential per gateway) and migrate
	// Supports both v1.0 and v2.0 legacy formats
	var legacyStore struct {
		Gateways map[string]*Credentials `json:"gateways"`
		Version  string                  `json:"version"`
	}

	if err := json.Unmarshal(data, &legacyStore); err != nil {
		return nil, fmt.Errorf("invalid credentials file format: %w", err)
	}

	if legacyStore.Version != "1.0" && legacyStore.Version != "2.0" {
		return nil, fmt.Errorf("unsupported credentials version %q; expected \"1.0\" or \"2.0\"", legacyStore.Version)
	}

	// Convert legacy format to enhanced format
	enhanced := &EnhancedCredentialStore{
		Gateways: make(map[string]*GatewayCredentials),
		Version:  "2.0",
	}

	for gwURL, legacyCred := range legacyStore.Gateways {
		if legacyCred == nil {
			// Create empty gateway entry
			enhanced.Gateways[gwURL] = &GatewayCredentials{
				Credentials:   []*Credentials{},
				DefaultIndex:  0,
				LastUsedIndex: 0,
			}
			continue
		}

		// Only add if it looks like a valid credential (has wallet or api key)
		if legacyCred.Wallet != "" || legacyCred.APIKey != "" {
			enhanced.Gateways[gwURL] = &GatewayCredentials{
				Credentials:   []*Credentials{legacyCred},
				DefaultIndex:  0,
				LastUsedIndex: 0,
			}
		} else {
			// Create empty gateway entry
			enhanced.Gateways[gwURL] = &GatewayCredentials{
				Credentials:   []*Credentials{},
				DefaultIndex:  0,
				LastUsedIndex: 0,
			}
		}
	}

	// Auto-save the migrated format
	if err := enhanced.Save(); err != nil {
		fmt.Fprintf(os.Stderr, "Warning: failed to save migrated credentials: %v\n", err)
	}

	return enhanced, nil
}

// Save saves the enhanced credential store
func (store *EnhancedCredentialStore) Save() error {
	credPath, err := GetCredentialsPath()
	if err != nil {
		return err
	}

	if store.Version == "" {
		store.Version = "2.0"
	}

	data, err := json.MarshalIndent(store, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal credentials: %w", err)
	}

	return os.WriteFile(credPath, data, 0600)
}

// AddCredential adds a new credential for the gateway
func (store *EnhancedCredentialStore) AddCredential(gatewayURL string, creds *Credentials) {
	if store.Gateways == nil {
		store.Gateways = make(map[string]*GatewayCredentials)
	}

	gatewayCredentials := store.Gateways[gatewayURL]
	if gatewayCredentials == nil {
		gatewayCredentials = &GatewayCredentials{
			Credentials:   []*Credentials{},
			DefaultIndex:  0,
			LastUsedIndex: 0,
		}
		store.Gateways[gatewayURL] = gatewayCredentials
	}

	// Check if credential already exists (by wallet address)
	for i, existing := range gatewayCredentials.Credentials {
		if strings.EqualFold(existing.Wallet, creds.Wallet) {
			// Update existing credential
			gatewayCredentials.Credentials[i] = creds
			return
		}
	}

	// Add new credential
	gatewayCredentials.Credentials = append(gatewayCredentials.Credentials, creds)
}

// GetDefaultCredential returns the default credential for a gateway
func (store *EnhancedCredentialStore) GetDefaultCredential(gatewayURL string) *Credentials {
	gatewayCredentials := store.Gateways[gatewayURL]
	if gatewayCredentials == nil || len(gatewayCredentials.Credentials) == 0 {
		return nil
	}

	// Ensure default index is valid
	if gatewayCredentials.DefaultIndex < 0 || gatewayCredentials.DefaultIndex >= len(gatewayCredentials.Credentials) {
		gatewayCredentials.DefaultIndex = 0
	}

	return gatewayCredentials.Credentials[gatewayCredentials.DefaultIndex]
}

// SetDefaultCredential sets the default credential by index
func (store *EnhancedCredentialStore) SetDefaultCredential(gatewayURL string, index int) bool {
	gatewayCredentials := store.Gateways[gatewayURL]
	if gatewayCredentials == nil || index < 0 || index >= len(gatewayCredentials.Credentials) {
		return false
	}

	gatewayCredentials.DefaultIndex = index
	gatewayCredentials.LastUsedIndex = index
	return true
}

// RemoveCredentialByNamespace removes the credential for a specific namespace from a gateway.
// Returns true if a credential was removed.
func (store *EnhancedCredentialStore) RemoveCredentialByNamespace(gatewayURL, namespace string) bool {
	gwCreds := store.Gateways[gatewayURL]
	if gwCreds == nil || len(gwCreds.Credentials) == 0 {
		return false
	}

	for i, cred := range gwCreds.Credentials {
		if cred.Namespace == namespace {
			// Remove this credential from the slice
			gwCreds.Credentials = append(gwCreds.Credentials[:i], gwCreds.Credentials[i+1:]...)

			// Fix indices if they now point beyond the slice
			if len(gwCreds.Credentials) == 0 {
				gwCreds.DefaultIndex = 0
				gwCreds.LastUsedIndex = 0
			} else {
				if gwCreds.DefaultIndex >= len(gwCreds.Credentials) {
					gwCreds.DefaultIndex = len(gwCreds.Credentials) - 1
				}
				if gwCreds.LastUsedIndex >= len(gwCreds.Credentials) {
					gwCreds.LastUsedIndex = gwCreds.DefaultIndex
				}
			}
			return true
		}
	}
	return false
}

// ClearAllCredentials removes all credentials
func (store *EnhancedCredentialStore) ClearAllCredentials() {
	store.Gateways = make(map[string]*GatewayCredentials)
}

// DisplayCredentialMenu shows the interactive credential selection menu
func (store *EnhancedCredentialStore) DisplayCredentialMenu(gatewayURL string) (AuthChoice, int, error) {
	gatewayCredentials := store.Gateways[gatewayURL]

	if gatewayCredentials == nil || len(gatewayCredentials.Credentials) == 0 {
		fmt.Println("\n🔐 No credentials found. Choose an option:")
		fmt.Println("1. Authenticate with new wallet")
		fmt.Println("2. Exit")
		fmt.Print("Choose (1-2): ")

		choice, err := readUserChoice(2)
		if err != nil {
			return AuthChoiceExit, -1, err
		}

		switch choice {
		case 1:
			return AuthChoiceAddCredential, -1, nil
		case 2:
			return AuthChoiceExit, -1, nil
		default:
			return AuthChoiceExit, -1, fmt.Errorf("invalid choice")
		}
	}

	fmt.Printf("\n🔐 Multiple wallets available for %s:\n", gatewayURL)

	// Display credentials
	for i, creds := range gatewayCredentials.Credentials {
		defaultMark := ""
		if i == gatewayCredentials.DefaultIndex {
			defaultMark = " (default)"
		}

		// Format wallet address for display
		displayAddr := creds.Wallet
		if len(displayAddr) > 10 {
			displayAddr = displayAddr[:6] + "..." + displayAddr[len(displayAddr)-4:]
		}

		statusEmoji := "✅"
		if !creds.IsValid() {
			statusEmoji = "❌"
		}

		planInfo := ""
		if creds.Plan != "" {
			planInfo = fmt.Sprintf(" (%s)", creds.Plan)
		}

		fmt.Printf("%d. %s %s%s%s\n", i+1, statusEmoji, displayAddr, planInfo, defaultMark)
	}

	fmt.Printf("%d. Add new wallet\n", len(gatewayCredentials.Credentials)+1)
	fmt.Printf("%d. Logout (clear all credentials)\n", len(gatewayCredentials.Credentials)+2)
	fmt.Printf("%d. Exit\n", len(gatewayCredentials.Credentials)+3)

	maxChoice := len(gatewayCredentials.Credentials) + 3
	fmt.Printf("Choose (1-%d): ", maxChoice)

	choice, err := readUserChoice(maxChoice)
	if err != nil {
		return AuthChoiceExit, -1, err
	}

	if choice <= len(gatewayCredentials.Credentials) {
		// User selected a credential
		return AuthChoiceUseCredential, choice - 1, nil
	} else if choice == len(gatewayCredentials.Credentials)+1 {
		// Add new credential
		return AuthChoiceAddCredential, -1, nil
	} else if choice == len(gatewayCredentials.Credentials)+2 {
		// Logout
		return AuthChoiceLogout, -1, nil
	} else {
		// Exit
		return AuthChoiceExit, -1, nil
	}
}

// readUserChoice reads and validates user input
func readUserChoice(maxChoice int) (int, error) {
	reader := bufio.NewReader(os.Stdin)
	input, err := reader.ReadString('\n')
	if err != nil {
		return 0, fmt.Errorf("failed to read input: %w", err)
	}

	choiceStr := strings.TrimSpace(input)
	choice, err := strconv.Atoi(choiceStr)
	if err != nil {
		return 0, fmt.Errorf("invalid input: please enter a number")
	}

	if choice < 1 || choice > maxChoice {
		return 0, fmt.Errorf("invalid choice: please enter a number between 1 and %d", maxChoice)
	}

	return choice, nil
}

// GetOrPromptForCredentials handles the complete authentication flow
func GetOrPromptForCredentials(gatewayURL string) (*Credentials, error) {
	store, err := LoadEnhancedCredentials()
	if err != nil {
		return nil, fmt.Errorf("failed to load credential store: %w", err)
	}

	// Check if we have a valid default credential
	defaultCreds := store.GetDefaultCredential(gatewayURL)
	if defaultCreds != nil && defaultCreds.IsValid() {
		// Update last used time
		defaultCreds.UpdateLastUsed()
		if err := store.Save(); err != nil {
			// Log warning but don't fail
			fmt.Fprintf(os.Stderr, "Warning: failed to update last used time: %v\n", err)
		}
		return defaultCreds, nil
	}

	// Need to prompt user for credential selection
	for {
		choice, credIndex, err := store.DisplayCredentialMenu(gatewayURL)
		if err != nil {
			return nil, fmt.Errorf("menu selection failed: %w", err)
		}

		switch choice {
		case AuthChoiceUseCredential:
			gatewayCredentials := store.Gateways[gatewayURL]
			if gatewayCredentials == nil || credIndex < 0 || credIndex >= len(gatewayCredentials.Credentials) {
				fmt.Println("❌ Invalid credential selection")
				continue
			}

			selectedCreds := gatewayCredentials.Credentials[credIndex]
			if !selectedCreds.IsValid() {
				fmt.Println("❌ Selected credentials are invalid or expired")
				continue
			}

			// Update default and last used
			store.SetDefaultCredential(gatewayURL, credIndex)
			selectedCreds.UpdateLastUsed()

			if err := store.Save(); err != nil {
				fmt.Fprintf(os.Stderr, "Warning: failed to save credentials: %v\n", err)
			}

			return selectedCreds, nil

		case AuthChoiceAddCredential:
			fmt.Println("\n🌐 Opening browser for wallet authentication...")
			newCreds, err := PerformWalletAuthentication(gatewayURL)
			if err != nil {
				fmt.Printf("❌ Authentication failed: %v\n", err)
				continue
			}

			// Add the new credential
			store.AddCredential(gatewayURL, newCreds)

			// Set as default if it's the first credential
			gatewayCredentials := store.Gateways[gatewayURL]
			if gatewayCredentials != nil && len(gatewayCredentials.Credentials) == 1 {
				store.SetDefaultCredential(gatewayURL, 0)
			}

			if err := store.Save(); err != nil {
				return nil, fmt.Errorf("failed to save new credentials: %w", err)
			}

			fmt.Printf("✅ Wallet %s added successfully\n", newCreds.Wallet)
			return newCreds, nil

		case AuthChoiceLogout:
			store.ClearAllCredentials()
			if err := store.Save(); err != nil {
				return nil, fmt.Errorf("failed to clear credentials: %w", err)
			}
			fmt.Println("✅ All credentials cleared")
			continue

		case AuthChoiceExit:
			return nil, fmt.Errorf("authentication cancelled by user")

		default:
			fmt.Println("❌ Invalid choice")
			continue
		}
	}
}

// HasValidEnhancedCredentials checks if there are valid credentials for the default gateway
func HasValidEnhancedCredentials() (bool, error) {
	store, err := LoadEnhancedCredentials()
	if err != nil {
		return false, err
	}

	gatewayURL := GetDefaultGatewayURL()
	defaultCreds := store.GetDefaultCredential(gatewayURL)

	return defaultCreds != nil && defaultCreds.IsValid(), nil
}

// GetValidEnhancedCredentials returns valid credentials for the default gateway
func GetValidEnhancedCredentials() (*Credentials, error) {
	store, err := LoadEnhancedCredentials()
	if err != nil {
		return nil, err
	}

	gatewayURL := GetDefaultGatewayURL()
	defaultCreds := store.GetDefaultCredential(gatewayURL)

	if defaultCreds == nil {
		return nil, fmt.Errorf("no credentials found for gateway %s", gatewayURL)
	}

	if !defaultCreds.IsValid() {
		return nil, fmt.Errorf("credentials for gateway %s are expired or invalid", gatewayURL)
	}

	return defaultCreds, nil
}
