package license

import (
	"crypto/ed25519"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"hash"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/sync/singleflight"
)

var (
	ErrLicenseMissing             = errors.New("license not configured")
	ErrLicenseBlocked             = errors.New("license is suspended or revoked")
	ErrLicenseExpired             = errors.New("license has expired")
	ErrLicenseGracePeriodExceeded = errors.New("license offline grace period exceeded")
	ErrLicenseTampered            = errors.New("license state signature invalid")
	ErrLicenseUnavailable         = errors.New("license service is unavailable")
	ErrLicenseClockInvalid        = errors.New("license clock anchor is invalid")
	ErrLicenseClockRolledBack     = errors.New("license clock anchor is rolled back")
)

const (
	maxTrustedClockDrift = 5 * time.Minute
	verificationCacheTTL = 10 * time.Second
)

type signedBlob struct {
	payload   []byte
	signature []byte
}

type licenseSnapshot struct {
	blob           signedBlob
	status         string
	forceBlock     bool
	serverIssuedAt time.Time
	localAnchor    time.Time
	cacheTag       []byte
	cacheExpiry    time.Time
	graceUntil     time.Time // non-zero while in offline grace period
	expiresAt      time.Time // license hard expiry; checked during grace
}

// Evaluator is the L1 in-memory authorization state machine.
type Evaluator struct {
	snapshot      atomic.Pointer[licenseSnapshot]
	sf            singleflight.Group
	publicKey     ed25519.PublicKey
	publicKeyOnce sync.Once
	sessionKey    [32]byte
	mu            sync.Mutex
	now           func() time.Time
	since         func(time.Time) time.Duration
}

func NewEvaluator(publicKey ed25519.PublicKey) *Evaluator {
	e := &Evaluator{publicKey: publicKey, now: time.Now, since: time.Since}
	if _, err := rand.Read(e.sessionKey[:]); err != nil {
		panic("license evaluator: failed to generate session key: " + err.Error())
	}
	e.snapshot.Store(&licenseSnapshot{})
	return e
}

func (e *Evaluator) Check() error {
	return e.CheckFeature("")
}

func (e *Evaluator) CheckFeature(feature string) error {
	s := e.snapshot.Load()
	if s == nil {
		return ErrLicenseMissing
	}
	if s.forceBlock {
		return e.blockedError(s)
	}
	if !s.graceUntil.IsZero() {
		now := e.now()
		if now.After(s.graceUntil) {
			return ErrLicenseGracePeriodExceeded
		}
		if !s.expiresAt.IsZero() && now.After(s.expiresAt) {
			return ErrLicenseExpired
		}
		return nil
	}
	if len(s.blob.payload) == 0 {
		return ErrLicenseMissing
	}
	if e.now().Before(s.cacheExpiry) && e.validCacheTag(s) {
		return e.checkPayload(s, feature)
	}

	res, err, _ := e.sf.Do("verify:"+strings.TrimSpace(feature), func() (any, error) {
		return e.revalidate(s, feature)
	})
	if err != nil {
		return err
	}
	if !res.(bool) {
		return e.blockedError(e.snapshot.Load())
	}
	return nil
}

func (e *Evaluator) checkPayload(snapshot *licenseSnapshot, feature string) error {
	lease, err := decodeLeasePayload(snapshot.blob.payload)
	if err != nil {
		return err
	}
	now, err := e.trustedNow(snapshot)
	if err != nil {
		return err
	}
	if lease.Status != "active" {
		return ErrLicenseBlocked
	}
	if now.After(lease.ExpiresAt) {
		return ErrLicenseExpired
	}
	if now.After(lease.ValidUntil) {
		return ErrLicenseUnavailable
	}
	if feature = strings.TrimSpace(feature); feature != "" && !lease.HasFeature(feature) {
		return fmt.Errorf("%w: %s", ErrLicenseFeatureMissing, feature)
	}
	return nil
}

func (e *Evaluator) blockedError(s *licenseSnapshot) error {
	if s != nil && (s.status == "suspended" || s.status == "revoked" || s.status == "selective_block") {
		return ErrLicenseBlocked
	}
	if s != nil && s.status == "unavailable" {
		return ErrLicenseUnavailable
	}
	if s == nil || len(s.blob.payload) == 0 {
		return ErrLicenseMissing
	}
	return e.checkPayload(s, "")
}

func (e *Evaluator) revalidate(s *licenseSnapshot, feature string) (bool, error) {
	if !ed25519.Verify(e.publicKey, s.blob.payload, s.blob.signature) {
		return false, ErrLicenseTampered
	}
	var payload struct {
		Status string `json:"status"`
	}
	if err := json.Unmarshal(s.blob.payload, &payload); err != nil {
		return false, fmt.Errorf("license state decode failed: %w", err)
	}
	checkErr := e.checkPayload(s, feature)
	next := &licenseSnapshot{
		blob:           s.blob,
		status:         payload.Status,
		forceBlock:     s.forceBlock,
		serverIssuedAt: s.serverIssuedAt,
		localAnchor:    s.localAnchor,
		cacheExpiry:    e.now().Add(verificationCacheTTL),
	}
	next.cacheTag = e.computeCacheTag(next)
	e.snapshot.Store(next)
	return checkErr == nil, nil
}

func (e *Evaluator) trustedNow(s *licenseSnapshot) (time.Time, error) {
	if s == nil || s.serverIssuedAt.IsZero() || s.localAnchor.IsZero() || e.now == nil || e.since == nil {
		return time.Time{}, ErrLicenseClockInvalid
	}
	elapsed := e.since(s.localAnchor)
	if elapsed < 0 {
		return time.Time{}, ErrLicenseClockInvalid
	}
	estimatedServerNow := s.serverIssuedAt.Add(elapsed)
	wallNow := e.now()
	drift := wallNow.Sub(estimatedServerNow)
	if drift < 0 {
		drift = -drift
	}
	if drift > maxTrustedClockDrift {
		return time.Time{}, fmt.Errorf("%w: local clock differs from signed server time by %s", ErrLicenseClockInvalid, drift.Round(time.Second))
	}
	if wallNow.After(estimatedServerNow) {
		return wallNow, nil
	}
	return estimatedServerNow, nil
}

func (e *Evaluator) computeCacheTag(s *licenseSnapshot) []byte {
	h := hmac.New(sha256.New, e.sessionKey[:])
	_, _ = h.Write(s.blob.payload)
	_, _ = h.Write(s.blob.signature)
	writeTimeHMAC(h, s.serverIssuedAt)
	writeTimeHMAC(h, s.localAnchor)
	writeTimeHMAC(h, s.cacheExpiry)
	return h.Sum(nil)
}

func writeTimeHMAC(h hash.Hash, value time.Time) {
	var raw [8]byte
	binary.BigEndian.PutUint64(raw[:], uint64(value.UnixNano()))
	_, _ = h.Write(raw[:])
}

func (e *Evaluator) validCacheTag(s *licenseSnapshot) bool {
	if len(s.cacheTag) != sha256.Size {
		return false
	}
	return hmac.Equal(s.cacheTag, e.computeCacheTag(s))
}

// LoadFromQuery initializes L1 from a signed Query or Heartbeat response.
func (e *Evaluator) LoadFromQuery(rawBody []byte, signatureField string) error {
	payload, sig, err := extractSignedPayload(rawBody, signatureField)
	if err != nil {
		return err
	}
	if !ed25519.Verify(e.publicKey, payload, sig) {
		return ErrLicenseTampered
	}
	lease, err := decodeLeasePayload(payload)
	if err != nil {
		return err
	}
	localAnchor := e.now()
	if err := validateLease(lease, localAnchor, "", false); err != nil {
		return err
	}
	b := signedBlob{payload: payload, signature: sig}
	next := &licenseSnapshot{
		blob:           b,
		status:         lease.Status,
		forceBlock:     lease.Status == "suspended" || lease.Status == "revoked",
		serverIssuedAt: lease.IssuedAt,
		localAnchor:    localAnchor,
		cacheExpiry:    localAnchor.Add(verificationCacheTTL),
	}
	next.cacheTag = e.computeCacheTag(next)
	e.snapshot.Store(next)
	return nil
}

func (e *Evaluator) SetBlocked(status string) {
	e.mu.Lock()
	defer e.mu.Unlock()
	old := e.snapshot.Load()
	next := licenseSnapshot{status: status, forceBlock: true}
	if old != nil {
		next = *old
		next.status = status
		next.forceBlock = true
		next.cacheTag = nil
		next.cacheExpiry = time.Time{}
	}
	e.snapshot.Store(&next)
}

func (e *Evaluator) SetUnavailable() {
	e.SetBlocked("unavailable")
}

// SetGrace transitions into an offline grace state. CheckFeature succeeds as
// long as wall time is within [now, graceUntil] and the license has not
// hard-expired at expiresAt. Clears any forceBlock — grace implies access
// is currently allowed.
func (e *Evaluator) SetGrace(expiresAt time.Time, graceUntil time.Time) {
	e.mu.Lock()
	defer e.mu.Unlock()
	old := e.snapshot.Load()
	next := licenseSnapshot{
		status:     "grace",
		graceUntil: graceUntil,
		expiresAt:  expiresAt,
	}
	if old != nil {
		next.blob = old.blob
		next.serverIssuedAt = old.serverIssuedAt
		next.localAnchor = old.localAnchor
	}
	e.snapshot.Store(&next)
}

// GraceUntil returns the time the offline grace period expires. Returns the
// zero time when not in a grace state.
func (e *Evaluator) GraceUntil() time.Time {
	s := e.snapshot.Load()
	if s == nil {
		return time.Time{}
	}
	return s.graceUntil
}

func (e *Evaluator) ClearForceBlock() {
	e.mu.Lock()
	defer e.mu.Unlock()
	old := e.snapshot.Load()
	if old == nil || !old.forceBlock {
		return
	}
	next := *old
	next.forceBlock = false
	next.cacheTag = nil
	next.cacheExpiry = time.Time{}
	e.snapshot.Store(&next)
}

func (e *Evaluator) SetPublicKey(key ed25519.PublicKey) {
	e.publicKeyOnce.Do(func() {
		e.mu.Lock()
		e.publicKey = key
		e.mu.Unlock()
	})
}

func (e *Evaluator) Status() string {
	s := e.snapshot.Load()
	if s == nil {
		return "missing"
	}
	if !s.graceUntil.IsZero() {
		return "grace"
	}
	if len(s.blob.payload) == 0 {
		return "missing"
	}
	return s.status
}

func extractSignedPayload(body []byte, signatureField string) ([]byte, []byte, error) {
	return extractSignedPayloadBytes(body, signatureField)
}
