package stepup

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/breakpilot/pca-platform/heuristic-service/internal/config"
)

// PoWService handles Proof-of-Work challenges
type PoWService struct {
	config     *config.PoWConfig
	challenges map[string]*PoWChallenge
	mu         sync.RWMutex
}

// PoWChallenge represents a Proof-of-Work challenge
type PoWChallenge struct {
	ID         string    `json:"id"`
	SessionID  string    `json:"session_id"`
	Challenge  string    `json:"challenge"`
	Difficulty int       `json:"difficulty"`
	CreatedAt  time.Time `json:"created_at"`
	ExpiresAt  time.Time `json:"expires_at"`
	Solved     bool      `json:"solved"`
}

// PoWChallengeResponse is sent to the client
type PoWChallengeResponse struct {
	ChallengeID string `json:"challenge_id"`
	Challenge   string `json:"challenge"`
	Difficulty  int    `json:"difficulty"`
	MaxTimeMs   int    `json:"max_time_ms"`
	Hint        string `json:"hint"`
}

// PoWVerifyRequest for verifying a solved challenge
type PoWVerifyRequest struct {
	SessionID   string `json:"session_id"`
	ChallengeID string `json:"challenge_id"`
	Challenge   string `json:"challenge"`
	Nonce       int64  `json:"nonce"`
}

// NewPoWService creates a new Proof-of-Work service
func NewPoWService(cfg *config.PoWConfig) *PoWService {
	return &PoWService{
		config:     cfg,
		challenges: make(map[string]*PoWChallenge),
	}
}

// CreateChallenge generates a new PoW challenge
func (s *PoWService) CreateChallenge(sessionID string) (*PoWChallengeResponse, error) {
	// Generate random challenge
	challengeBytes := make([]byte, 16)
	if _, err := rand.Read(challengeBytes); err != nil {
		return nil, err
	}
	challengeStr := hex.EncodeToString(challengeBytes)

	// Generate challenge ID
	idBytes := make([]byte, 8)
	rand.Read(idBytes)
	challengeID := hex.EncodeToString(idBytes)

	// Create challenge
	challenge := &PoWChallenge{
		ID:         challengeID,
		SessionID:  sessionID,
		Challenge:  challengeStr,
		Difficulty: s.config.Difficulty,
		CreatedAt:  time.Now(),
		ExpiresAt:  time.Now().Add(time.Duration(s.config.MaxDurationMs*2) * time.Millisecond),
		Solved:     false,
	}

	// Store challenge
	s.mu.Lock()
	s.challenges[challengeID] = challenge
	s.mu.Unlock()

	// Build response
	prefix := strings.Repeat("0", s.config.Difficulty)
	response := &PoWChallengeResponse{
		ChallengeID: challengeID,
		Challenge:   challengeStr,
		Difficulty:  s.config.Difficulty,
		MaxTimeMs:   s.config.MaxDurationMs,
		Hint:        fmt.Sprintf("Find nonce where SHA256(challenge + nonce) starts with '%s'", prefix),
	}

	return response, nil
}

// VerifyChallenge verifies a PoW solution
func (s *PoWService) VerifyChallenge(req *PoWVerifyRequest) (bool, error) {
	s.mu.RLock()
	challenge, exists := s.challenges[req.ChallengeID]
	s.mu.RUnlock()

	if !exists {
		return false, nil
	}

	// Check expiration
	if time.Now().After(challenge.ExpiresAt) {
		s.mu.Lock()
		delete(s.challenges, req.ChallengeID)
		s.mu.Unlock()
		return false, nil
	}

	// Check session match
	if challenge.SessionID != req.SessionID {
		return false, nil
	}

	// Check challenge string match
	if challenge.Challenge != req.Challenge {
		return false, nil
	}

	// Verify the proof of work
	input := fmt.Sprintf("%s%d", req.Challenge, req.Nonce)
	hash := sha256.Sum256([]byte(input))
	hashHex := hex.EncodeToString(hash[:])

	// Check if hash has required number of leading zeros
	prefix := strings.Repeat("0", challenge.Difficulty)
	if !strings.HasPrefix(hashHex, prefix) {
		return false, nil
	}

	// Mark as solved
	s.mu.Lock()
	challenge.Solved = true
	s.mu.Unlock()

	return true, nil
}

// VerifyProof is a standalone verification without stored challenge
// Useful for quick verification
func (s *PoWService) VerifyProof(challenge string, nonce int64, difficulty int) bool {
	input := fmt.Sprintf("%s%d", challenge, nonce)
	hash := sha256.Sum256([]byte(input))
	hashHex := hex.EncodeToString(hash[:])

	prefix := strings.Repeat("0", difficulty)
	return strings.HasPrefix(hashHex, prefix)
}

// CleanupExpiredChallenges removes expired challenges
func (s *PoWService) CleanupExpiredChallenges() {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now()
	for id, challenge := range s.challenges {
		if now.After(challenge.ExpiresAt) {
			delete(s.challenges, id)
		}
	}
}

// IsEnabled returns whether PoW is enabled
func (s *PoWService) IsEnabled() bool {
	return s.config.Enabled
}

// GetDifficulty returns configured difficulty
func (s *PoWService) GetDifficulty() int {
	return s.config.Difficulty
}
