// internal/auth/auth.go
package auth

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/nbd-wtf/go-nostr"
	"git.sovbit.dev/Enki/nostr-poster/internal/db"
	"go.uber.org/zap"
)

var (
	// ErrInvalidSignature is returned when a signature is invalid
	ErrInvalidSignature = errors.New("invalid signature")
	
	// ErrTokenExpired is returned when a token has expired
	ErrTokenExpired = errors.New("token expired")
	
	// ErrInvalidToken is returned when a token is invalid
	ErrInvalidToken = errors.New("invalid token")
)

// Token represents an authentication token
type Token struct {
	Pubkey    string    `json:"pubkey"`
	IssuedAt  time.Time `json:"issued_at"`
	ExpiresAt time.Time `json:"expires_at"`
	Nonce     string    `json:"nonce"`
}

// Service provides authentication functionality
type Service struct {
	db             *db.DB
	logger         *zap.Logger
	secretKey      []byte
	tokenDuration  time.Duration
}

// NewService creates a new authentication service
func NewService(db *db.DB, logger *zap.Logger, secretKey string, tokenDuration time.Duration) *Service {
	// If no secret key is provided, generate a secure random one
	decodedKey := []byte(secretKey)
	if secretKey == "" {
		// Generate a secure random key
		key := make([]byte, 32)
		if _, err := rand.Read(key); err != nil {
			logger.Fatal("Failed to generate random key for auth service", zap.Error(err))
		}
		decodedKey = key
	}
	
	return &Service{
		db:            db,
		logger:        logger,
		secretKey:     decodedKey,
		tokenDuration: tokenDuration,
	}
}

// Login handles user login with a Nostr signature
func (s *Service) Login(pubkey, signature, eventJSON string) (string, error) {
	// Parse the event
	var event nostr.Event
	if err := json.Unmarshal([]byte(eventJSON), &event); err != nil {
		return "", fmt.Errorf("failed to parse event: %w", err)
	}
	
	// Verify the event
	if event.PubKey != pubkey {
		return "", errors.New("pubkey mismatch in event")
	}
	
	// Verify the signature
	ok, err := event.CheckSignature()
	if err != nil {
		return "", fmt.Errorf("failed to check signature: %w", err)
	}
	if !ok {
		return "", ErrInvalidSignature
	}
	
	// Check if the event was created recently
	now := time.Now()
	eventTime := time.Unix(int64(event.CreatedAt), 0)
	if now.Sub(eventTime) > 5*time.Minute || eventTime.After(now.Add(5*time.Minute)) {
		return "", errors.New("event timestamp is too far from current time")
	}
	
	// Generate a token
	token, err := s.createToken(pubkey)
	if err != nil {
		return "", fmt.Errorf("failed to create token: %w", err)
	}
	
	return token, nil
}

// VerifyToken validates an authentication token
func (s *Service) VerifyToken(tokenStr string) (string, error) {
	// Remove the "Bearer " prefix if present
	tokenStr = strings.TrimPrefix(tokenStr, "Bearer ")
	
	// Decode the token
	tokenData, err := base64.StdEncoding.DecodeString(tokenStr)
	if err != nil {
		return "", ErrInvalidToken
	}
	
	// Parse the token
	var token Token
	if err := json.Unmarshal(tokenData, &token); err != nil {
		return "", ErrInvalidToken
	}
	
	// Check if the token has expired
	if time.Now().After(token.ExpiresAt) {
		return "", ErrTokenExpired
	}
	
	return token.Pubkey, nil
}

// CreateNIP07Challenge creates a challenge for NIP-07 authentication
func (s *Service) CreateNIP07Challenge(pubkey string) (string, error) {
	// Generate a nonce
	nonce := make([]byte, 16)
	if _, err := rand.Read(nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}
	nonceStr := hex.EncodeToString(nonce)
	
	// Create the challenge event
	event := nostr.Event{
		PubKey:    pubkey,
		CreatedAt: nostr.Timestamp(time.Now().Unix()),
		Kind:      22242, // Ephemeral event for authentication
		Tags:      []nostr.Tag{{"challenge", nonceStr}},
		Content:   "Please sign this event to authenticate with Nostr Poster",
	}
	
	// Set the ID
	event.ID = event.GetID()
	
	// Convert to JSON
	eventJSON, err := json.Marshal(event)
	if err != nil {
		return "", fmt.Errorf("failed to serialize event: %w", err)
	}
	
	return string(eventJSON), nil
}

// createToken creates an authentication token
func (s *Service) createToken(pubkey string) (string, error) {
	// Generate a nonce
	nonce := make([]byte, 8)
	if _, err := rand.Read(nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}
	nonceStr := hex.EncodeToString(nonce)
	
	// Create the token
	now := time.Now()
	token := Token{
		Pubkey:    pubkey,
		IssuedAt:  now,
		ExpiresAt: now.Add(s.tokenDuration),
		Nonce:     nonceStr,
	}
	
	// Serialize the token
	tokenData, err := json.Marshal(token)
	if err != nil {
		return "", fmt.Errorf("failed to serialize token: %w", err)
	}
	
	// Encode the token
	tokenStr := base64.StdEncoding.EncodeToString(tokenData)
	
	return tokenStr, nil
}