package bkosmi

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
)

// TokenCache represents a cached JWT token with metadata
type TokenCache struct {
	Token     string    `json:"token"`
	Email     string    `json:"email"`      // To verify it's for the right account
	ExpiresAt time.Time `json:"expires_at"` // When the token expires
	SavedAt   time.Time `json:"saved_at"`   // When we cached it
}

const (
	tokenCacheFile = "kosmi_token_cache.json"
	// Refresh token if it expires within this window
	tokenRefreshBuffer = 7 * 24 * time.Hour // 1 week before expiry
)

// getTokenCachePath returns the path to the token cache file
// Prioritizes MATTERBRIDGE_DATA_DIR env var, falls back to ~/.matterbridge
func getTokenCachePath() (string, error) {
	var cacheDir string
	
	// Check for environment variable (for Docker volumes)
	if dataDir := os.Getenv("MATTERBRIDGE_DATA_DIR"); dataDir != "" {
		cacheDir = dataDir
	} else {
		// Fall back to home directory for local development
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("failed to get home directory: %w", err)
		}
		cacheDir = filepath.Join(homeDir, ".matterbridge")
	}
	
	// Create cache directory if it doesn't exist
	if err := os.MkdirAll(cacheDir, 0700); err != nil {
		return "", fmt.Errorf("failed to create cache directory: %w", err)
	}

	return filepath.Join(cacheDir, tokenCacheFile), nil
}

// parseJWTExpiry extracts the expiry time from a JWT token
func parseJWTExpiry(token string) (time.Time, error) {
	// JWT format: header.payload.signature
	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		return time.Time{}, fmt.Errorf("invalid JWT format")
	}

	// Decode the payload (second part)
	payload := parts[1]
	
	// JWT uses base64url encoding, which may not have padding
	// Add padding if needed
	if len(payload)%4 != 0 {
		payload += strings.Repeat("=", 4-len(payload)%4)
	}

	decoded, err := base64.URLEncoding.DecodeString(payload)
	if err != nil {
		// Try standard base64 if URL encoding fails
		decoded, err = base64.StdEncoding.DecodeString(payload)
		if err != nil {
			return time.Time{}, fmt.Errorf("failed to decode JWT payload: %w", err)
		}
	}

	// Parse the JSON payload
	var claims struct {
		Exp int64 `json:"exp"` // Expiry time as Unix timestamp
	}

	if err := json.Unmarshal(decoded, &claims); err != nil {
		return time.Time{}, fmt.Errorf("failed to parse JWT claims: %w", err)
	}

	if claims.Exp == 0 {
		return time.Time{}, fmt.Errorf("no expiry time in JWT")
	}

	return time.Unix(claims.Exp, 0), nil
}

// loadTokenCache loads a cached token from disk
func loadTokenCache(email string, log *logrus.Entry) (*TokenCache, error) {
	cachePath, err := getTokenCachePath()
	if err != nil {
		return nil, fmt.Errorf("failed to get cache path: %w", err)
	}

	// Check if cache file exists
	if _, err := os.Stat(cachePath); os.IsNotExist(err) {
		return nil, nil // No cache file, not an error
	}

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

	// Parse cache
	var cache TokenCache
	if err := json.Unmarshal(data, &cache); err != nil {
		return nil, fmt.Errorf("failed to parse cache file: %w", err)
	}

	// Verify it's for the correct email
	if cache.Email != email {
		log.Debugf("Cached token is for different email (%s vs %s), ignoring", cache.Email, email)
		return nil, nil
	}

	// Check if token is expired or close to expiring
	now := time.Now()
	if now.After(cache.ExpiresAt) {
		log.Info("Cached token has expired")
		return nil, nil
	}

	if now.After(cache.ExpiresAt.Add(-tokenRefreshBuffer)) {
		log.Infof("Cached token expires soon (%s), will refresh", cache.ExpiresAt.Format(time.RFC3339))
		return nil, nil
	}

	timeUntilExpiry := cache.ExpiresAt.Sub(now)
	log.Infof("✅ Using cached token (expires in %s)", timeUntilExpiry.Round(24*time.Hour))
	
	return &cache, nil
}

// saveTokenCache saves a token to disk
func saveTokenCache(token, email string, log *logrus.Entry) error {
	cachePath, err := getTokenCachePath()
	if err != nil {
		return fmt.Errorf("failed to get cache path: %w", err)
	}

	// Parse token expiry
	expiresAt, err := parseJWTExpiry(token)
	if err != nil {
		log.Warnf("Failed to parse token expiry, will not cache: %v", err)
		return nil // Don't fail the connection, just skip caching
	}

	// Create cache structure
	cache := TokenCache{
		Token:     token,
		Email:     email,
		ExpiresAt: expiresAt,
		SavedAt:   time.Now(),
	}

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

	// Write to file with restricted permissions
	if err := os.WriteFile(cachePath, data, 0600); err != nil {
		return fmt.Errorf("failed to write cache file: %w", err)
	}

	timeUntilExpiry := expiresAt.Sub(time.Now())
	log.Infof("💾 Token cached (expires in %s)", timeUntilExpiry.Round(24*time.Hour))

	return nil
}

// clearTokenCache removes the cached token
func clearTokenCache(log *logrus.Entry) error {
	cachePath, err := getTokenCachePath()
	if err != nil {
		return fmt.Errorf("failed to get cache path: %w", err)
	}

	if err := os.Remove(cachePath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove cache file: %w", err)
	}

	log.Debug("Token cache cleared")
	return nil
}

