package auth

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/DeBrosOfficial/network/pkg/tlsutil"
	qrterminal "github.com/mdp/qrterminal/v3"
)

// defaultPhantomAuthURL is the default Phantom auth React app URL (deployed on Orama devnet).
// Override with ORAMA_PHANTOM_AUTH_URL environment variable.
const defaultPhantomAuthURL = "https://phantom-auth-y0w9aa.orama-devnet.network"

// phantomAuthURL returns the Phantom auth URL, preferring the environment variable.
func phantomAuthURL() string {
	if u := os.Getenv("ORAMA_PHANTOM_AUTH_URL"); u != "" {
		return strings.TrimRight(u, "/")
	}
	return defaultPhantomAuthURL
}

// PhantomSession represents a phantom auth session from the gateway.
type PhantomSession struct {
	SessionID string `json:"session_id"`
	ExpiresAt string `json:"expires_at"`
}

// PhantomSessionStatus represents the polled status of a phantom auth session.
type PhantomSessionStatus struct {
	SessionID string `json:"session_id"`
	Status    string `json:"status"`
	Wallet    string `json:"wallet"`
	APIKey    string `json:"api_key"`
	Namespace string `json:"namespace"`
	Error     string `json:"error"`
}

// PerformPhantomAuthentication runs the Phantom Solana auth flow:
// 1. Prompt for namespace
// 2. Create session via gateway
// 3. Display QR code in terminal
// 4. Poll for completion
// 5. Return credentials
func PerformPhantomAuthentication(gatewayURL, namespace string) (*Credentials, error) {
	reader := bufio.NewReader(os.Stdin)

	fmt.Println("\n🟣 Phantom Wallet Authentication (Solana)")
	fmt.Println("==========================================")
	fmt.Println("Requires an NFT from the authorized collection.")

	// Prompt for namespace if empty
	if namespace == "" {
		for {
			fmt.Print("Enter namespace (required): ")
			nsInput, err := reader.ReadString('\n')
			if err != nil {
				return nil, fmt.Errorf("failed to read namespace: %w", err)
			}
			namespace = strings.TrimSpace(nsInput)
			if namespace != "" {
				break
			}
			fmt.Println("Namespace cannot be empty.")
		}
	}

	domain := extractDomainFromURL(gatewayURL)
	client := tlsutil.NewHTTPClientForDomain(30*time.Second, domain)

	// 1. Create phantom session
	fmt.Println("\nCreating authentication session...")
	session, err := createPhantomSession(client, gatewayURL, namespace)
	if err != nil {
		return nil, fmt.Errorf("failed to create session: %w", err)
	}

	// 2. Build auth URL and display QR code
	authURL := fmt.Sprintf("%s/?session=%s&gateway=%s&namespace=%s",
		phantomAuthURL(), session.SessionID, url.QueryEscape(gatewayURL), url.QueryEscape(namespace))

	fmt.Println("\nScan this QR code with your phone to authenticate:")
	fmt.Println()
	qrterminal.GenerateWithConfig(authURL, qrterminal.Config{
		Level:     qrterminal.M,
		Writer:    os.Stdout,
		BlackChar: qrterminal.BLACK,
		WhiteChar: qrterminal.WHITE,
		QuietZone: 1,
	})
	fmt.Println()
	fmt.Printf("Or open this URL on your phone:\n%s\n\n", authURL)
	fmt.Println("Waiting for authentication... (timeout: 5 minutes)")

	// 3. Poll for completion
	creds, err := pollPhantomSession(client, gatewayURL, session.SessionID)
	if err != nil {
		return nil, err
	}

	// Set namespace and build namespace URL
	creds.Namespace = namespace
	if domain := extractDomainFromURL(gatewayURL); domain != "" {
		if namespace == "default" {
			creds.NamespaceURL = fmt.Sprintf("https://%s", domain)
		} else {
			creds.NamespaceURL = fmt.Sprintf("https://ns-%s.%s", namespace, domain)
		}
	}

	fmt.Printf("\n🎉 Authentication successful!\n")
	truncatedKey := creds.APIKey
	if len(truncatedKey) > 8 {
		truncatedKey = truncatedKey[:8] + "..."
	}
	fmt.Printf("📝 API Key: %s\n", truncatedKey)

	return creds, nil
}

// createPhantomSession creates a new phantom auth session via the gateway.
func createPhantomSession(client *http.Client, gatewayURL, namespace string) (*PhantomSession, error) {
	reqBody := map[string]string{
		"namespace": namespace,
	}
	payload, err := json.Marshal(reqBody)
	if err != nil {
		return nil, err
	}

	resp, err := client.Post(gatewayURL+"/v1/auth/phantom/session", "application/json", bytes.NewReader(payload))
	if err != nil {
		return nil, fmt.Errorf("failed to call gateway: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("gateway returned status %d: %s", resp.StatusCode, string(body))
	}

	var session PhantomSession
	if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &session, nil
}

// pollPhantomSession polls the gateway for session completion.
func pollPhantomSession(client *http.Client, gatewayURL, sessionID string) (*Credentials, error) {
	pollInterval := 2 * time.Second
	maxDuration := 5 * time.Minute
	deadline := time.Now().Add(maxDuration)

	spinnerChars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
	spinnerIdx := 0

	for time.Now().Before(deadline) {
		resp, err := client.Get(gatewayURL + "/v1/auth/phantom/session/" + sessionID)
		if err != nil {
			time.Sleep(pollInterval)
			continue
		}

		var status PhantomSessionStatus
		if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
			resp.Body.Close()
			time.Sleep(pollInterval)
			continue
		}
		resp.Body.Close()

		switch status.Status {
		case "completed":
			fmt.Printf("\r✅ Authenticated!                    \n")
			return &Credentials{
				APIKey:   status.APIKey,
				Wallet:   status.Wallet,
				UserID:   status.Wallet,
				IssuedAt: time.Now(),
			}, nil

		case "failed":
			fmt.Printf("\r❌ Authentication failed              \n")
			errMsg := status.Error
			if errMsg == "" {
				errMsg = "unknown error"
			}
			return nil, fmt.Errorf("authentication failed: %s", errMsg)

		case "expired":
			fmt.Printf("\r⏰ Session expired                   \n")
			return nil, fmt.Errorf("authentication session expired")

		case "pending":
			fmt.Printf("\r%s Waiting for phone authentication... ", spinnerChars[spinnerIdx%len(spinnerChars)])
			spinnerIdx++
		}

		time.Sleep(pollInterval)
	}

	fmt.Printf("\r⏰ Timeout                           \n")
	return nil, fmt.Errorf("authentication timed out after 5 minutes")
}
