package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"

    jwt "github.com/golang-jwt/jwt/v5"
    "github.com/pquerna/otp/totp"
)

// Simple in-memory storage for TOTPs per user. In production, use a DB.
var totpSecrets = map[string]string{}

// API key (default) - can be overridden by WG_API_KEY env var.
var apiKey string

// JWT secret (default) - can be overridden by JWT_SECRET env var.
var jwtSecret string

func init() {
    apiKey = os.Getenv("WG_API_KEY")
    if apiKey == "" {
        apiKey = "test-api-key" // default for testing
    }
    jwtSecret = os.Getenv("JWT_SECRET")
    if jwtSecret == "" {
        jwtSecret = "secret" // default for testing
    }
    // Preload a test user so JWTs can be generated in tests if needed
    if _, ok := totpSecrets["test"]; !ok {
        // generate a random-looking secret for test user if desired
        // but we won't force it here; user can call /auth/setup-totp?user=test
    }
}

type Claims struct {
    Username string                 `json:"username"`
    jwt.RegisteredClaims
}

// generateJWT creates a short-lived JWT for a given username
func generateJWT(username string) (string, error) {
    claims := &Claims{
        Username: username,
        RegisteredClaims: jwt.RegisteredClaims{
            Issuer:    "wgrplane",
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(jwtSecret))
}

func parseJWT(tokenStr string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return []byte(jwtSecret), nil
    })
    if err != nil {
        return nil, err
    }
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    return nil, fmt.Errorf("invalid token")
}

// totpSetupHandler returns a new TOTP secret and provisioning URI for a user
func totpSetupHandler(w http.ResponseWriter, r *http.Request) {
    user := r.URL.Query().Get("user")
    if user == "" {
        http.Error(w, "missing user", http.StatusBadRequest)
        return
    }
    key, err := totp.Generate(totp.GenerateOpts{
        Issuer:      "WGRplane",
        AccountName: user,
    })
    if err != nil {
        http.Error(w, "failed to generate secret", http.StatusInternalServerError)
        return
    }
    secret := key.Secret()
    totpSecrets[user] = secret
    resp := map[string]string{
        "secret":          secret,
        "provisioning_uri": key.URL(),
    }
    w.Header().Set("Content-Type", "application/json")
    _ = json.NewEncoder(w).Encode(resp)
}

// AuthMiddleware protects selected routes with API Key or JWT + optional TOTP
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 1) API Key path
        if key := r.Header.Get("wg-rplane-datadunia"); key != "" {
            if subtleConstantTimeEquals(key, apiKey) {
                // If user has a TOTP, require current OTP in header
                // The username isn't known from API Key alone; skip TOTP check here
                next.ServeHTTP(w, r)
                return
            }
        }

        // 2) JWT path
        authHeader := r.Header.Get("Authorization")
        if strings.HasPrefix(authHeader, "Bearer ") {
            token := strings.TrimPrefix(authHeader, "Bearer ")
            claims, err := parseJWT(token)
            if err != nil {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }
            // If user has a TOTp secret, verify it
            if secret, ok := totpSecrets[claims.Username]; ok {
                otp := r.Header.Get("X-TOTP")
                if otp == "" || !totp.Validate(otp, secret) {
                    http.Error(w, "Unauthorized (TOTp)", http.StatusUnauthorized)
                    return
                }
            }
            // Attach username to context for downstream handlers if needed
            next.ServeHTTP(w, r)
            return
        }

        // No valid auth provided
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
    })
}

// helper for constant-time string comparison
func subtleConstantTimeEquals(a, b string) bool {
    if len(a) != len(b) {
        return false
    }
    var diff byte
    for i := 0; i < len(a); i++ {
        diff |= a[i] ^ b[i]
    }
    return diff == 0
}
