package directory

import (
	"crypto/rand"
	"strconv"
	"strings"
	"time"

	"lol.mleku.dev/chk"
	"lol.mleku.dev/errorf"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/tag"
)

// TrustAct represents a complete Trust Act event (Kind 39101)
// with typed access to its components.
type TrustAct struct {
	Event            *event.E
	TargetPubkey     string
	TrustLevel       TrustLevel
	RelayURL         string
	Expiry           *time.Time
	Reason           TrustReason
	ReplicationKinds []uint16
	IdentityTag      *IdentityTag
}

// IdentityTag represents the I tag with npub identity and proof-of-control.
type IdentityTag struct {
	NPubIdentity string
	Nonce        string
	Signature    string
}

// NewTrustAct creates a new Trust Act event.
func NewTrustAct(
	pubkey []byte,
	targetPubkey string,
	trustLevel TrustLevel,
	relayURL string,
	expiry *time.Time,
	reason TrustReason,
	replicationKinds []uint16,
	identityTag *IdentityTag,
) (ta *TrustAct, err error) {

	// Validate required fields
	if len(pubkey) != 32 {
		return nil, errorf.E("pubkey must be 32 bytes")
	}
	if targetPubkey == "" {
		return nil, errorf.E("target pubkey is required")
	}
	if len(targetPubkey) != 64 {
		return nil, errorf.E("target pubkey must be 64 hex characters")
	}
	if err = ValidateTrustLevel(trustLevel); chk.E(err) {
		return
	}
	if relayURL == "" {
		return nil, errorf.E("relay URL is required")
	}

	// Create base event
	ev := CreateBaseEvent(pubkey, TrustActKind)

	// Add required tags
	ev.Tags.Append(tag.NewFromAny(string(PubkeyTag), targetPubkey))
	ev.Tags.Append(tag.NewFromAny(string(TrustLevelTag), strconv.FormatUint(uint64(trustLevel), 10)))
	ev.Tags.Append(tag.NewFromAny(string(RelayTag), relayURL))

	// Add optional expiry
	if expiry != nil {
		ev.Tags.Append(tag.NewFromAny(string(ExpiryTag), strconv.FormatInt(expiry.Unix(), 10)))
	}

	// Add reason
	if reason != "" {
		ev.Tags.Append(tag.NewFromAny(string(ReasonTag), string(reason)))
	}

	// Add replication kinds (K tag)
	if len(replicationKinds) > 0 {
		var kindStrings []string
		for _, k := range replicationKinds {
			kindStrings = append(kindStrings, strconv.FormatUint(uint64(k), 10))
		}
		ev.Tags.Append(tag.NewFromAny(string(KTag), strings.Join(kindStrings, ",")))
	}

	// Add identity tag if provided
	if identityTag != nil {
		if err = identityTag.Validate(); chk.E(err) {
			return
		}
		ev.Tags.Append(tag.NewFromAny(string(ITag),
			identityTag.NPubIdentity,
			identityTag.Nonce,
			identityTag.Signature))
	}

	ta = &TrustAct{
		Event:            ev,
		TargetPubkey:     targetPubkey,
		TrustLevel:       trustLevel,
		RelayURL:         relayURL,
		Expiry:           expiry,
		Reason:           reason,
		ReplicationKinds: replicationKinds,
		IdentityTag:      identityTag,
	}

	return
}

// ParseTrustAct parses an event into a TrustAct structure
// with validation.
func ParseTrustAct(ev *event.E) (ta *TrustAct, err error) {
	if ev == nil {
		return nil, errorf.E("event cannot be nil")
	}

	// Validate event kind
	if ev.Kind != TrustActKind.K {
		return nil, errorf.E("invalid event kind: expected %d, got %d",
			TrustActKind.K, ev.Kind)
	}

	// Extract required tags
	pTag := ev.Tags.GetFirst(PubkeyTag)
	if pTag == nil {
		return nil, errorf.E("missing p tag")
	}

	trustLevelTag := ev.Tags.GetFirst(TrustLevelTag)
	if trustLevelTag == nil {
		return nil, errorf.E("missing trust_level tag")
	}

	relayTag := ev.Tags.GetFirst(RelayTag)
	if relayTag == nil {
		return nil, errorf.E("missing relay tag")
	}

	// Validate trust level
	var trustLevelValue uint64
	if trustLevelValue, err = strconv.ParseUint(string(trustLevelTag.Value()), 10, 8); chk.E(err) {
		return nil, errorf.E("invalid trust level: %w", err)
	}
	trustLevel := TrustLevel(trustLevelValue)
	if err = ValidateTrustLevel(trustLevel); chk.E(err) {
		return
	}

	// Parse optional expiry
	var expiry *time.Time
	expiryTag := ev.Tags.GetFirst(ExpiryTag)
	if expiryTag != nil {
		var expiryUnix int64
		if expiryUnix, err = strconv.ParseInt(string(expiryTag.Value()), 10, 64); chk.E(err) {
			return nil, errorf.E("invalid expiry timestamp: %w", err)
		}
		expiryTime := time.Unix(expiryUnix, 0)
		expiry = &expiryTime
	}

	// Parse optional reason
	var reason TrustReason
	reasonTag := ev.Tags.GetFirst(ReasonTag)
	if reasonTag != nil {
		reason = TrustReason(reasonTag.Value())
	}

	// Parse replication kinds (K tag)
	var replicationKinds []uint16
	kTag := ev.Tags.GetFirst(KTag)
	if kTag != nil {
		kindStrings := strings.Split(string(kTag.Value()), ",")
		for _, kindStr := range kindStrings {
			kindStr = strings.TrimSpace(kindStr)
			if kindStr == "" {
				continue
			}
			var kind uint64
			if kind, err = strconv.ParseUint(kindStr, 10, 16); chk.E(err) {
				return nil, errorf.E("invalid kind in K tag: %s", kindStr)
			}
			replicationKinds = append(replicationKinds, uint16(kind))
		}
	}

	// Parse identity tag (I tag)
	var identityTag *IdentityTag
	iTag := ev.Tags.GetFirst(ITag)
	if iTag != nil {
		if identityTag, err = ParseIdentityTag(iTag); chk.E(err) {
			return
		}
	}

	ta = &TrustAct{
		Event:            ev,
		TargetPubkey:     string(pTag.ValueHex()), // ValueHex() handles binary/hex storage
		TrustLevel:       trustLevel,
		RelayURL:         string(relayTag.Value()),
		Expiry:           expiry,
		Reason:           reason,
		ReplicationKinds: replicationKinds,
		IdentityTag:      identityTag,
	}

	return
}

// ParseIdentityTag parses an I tag into an IdentityTag structure.
func ParseIdentityTag(t *tag.T) (it *IdentityTag, err error) {
	if t == nil {
		return nil, errorf.E("tag cannot be nil")
	}

	if t.Len() < 4 {
		return nil, errorf.E("I tag must have at least 4 elements")
	}

	// First element should be "I"
	if string(t.T[0]) != "I" {
		return nil, errorf.E("invalid I tag key")
	}

	it = &IdentityTag{
		NPubIdentity: string(t.T[1]),
		Nonce:        string(t.T[2]),
		Signature:    string(t.T[3]),
	}

	if err = it.Validate(); chk.E(err) {
		return nil, err
	}
	return it, nil
}

// Validate performs validation of an IdentityTag.
func (it *IdentityTag) Validate() (err error) {
	if it == nil {
		return errorf.E("IdentityTag cannot be nil")
	}

	if it.NPubIdentity == "" {
		return errorf.E("npub identity is required")
	}

	if !strings.HasPrefix(it.NPubIdentity, "npub1") {
		return errorf.E("identity must be npub-encoded")
	}

	if it.Nonce == "" {
		return errorf.E("nonce is required")
	}

	if len(it.Nonce) < 32 { // Minimum 16 bytes hex-encoded
		return errorf.E("nonce must be at least 16 bytes (32 hex characters)")
	}

	if it.Signature == "" {
		return errorf.E("signature is required")
	}

	if len(it.Signature) != 128 { // 64 bytes hex-encoded
		return errorf.E("signature must be 64 bytes (128 hex characters)")
	}

	return nil
}

// Validate performs comprehensive validation of a TrustAct.
func (ta *TrustAct) Validate() (err error) {
	if ta == nil {
		return errorf.E("TrustAct cannot be nil")
	}

	if ta.Event == nil {
		return errorf.E("event cannot be nil")
	}

	// Validate event signature
	if _, err = ta.Event.Verify(); chk.E(err) {
		return errorf.E("invalid event signature: %w", err)
	}

	// Validate required fields
	if ta.TargetPubkey == "" {
		return errorf.E("target pubkey is required")
	}

	if len(ta.TargetPubkey) != 64 {
		return errorf.E("target pubkey must be 64 hex characters")
	}

	if err = ValidateTrustLevel(ta.TrustLevel); chk.E(err) {
		return
	}

	if ta.RelayURL == "" {
		return errorf.E("relay URL is required")
	}

	// Validate expiry if present
	if ta.Expiry != nil && ta.Expiry.Before(time.Now()) {
		return errorf.E("trust act has expired")
	}

	// Validate identity tag if present
	if ta.IdentityTag != nil {
		if err = ta.IdentityTag.Validate(); chk.E(err) {
			return
		}
	}

	return nil
}

// IsExpired returns true if the trust act has expired.
func (ta *TrustAct) IsExpired() bool {
	return ta.Expiry != nil && ta.Expiry.Before(time.Now())
}

// HasReplicationKind returns true if the act includes the specified
// kind for replication.
func (ta *TrustAct) HasReplicationKind(kind uint16) bool {
	for _, k := range ta.ReplicationKinds {
		if k == kind {
			return true
		}
	}
	return false
}

// ShouldReplicate returns true if an event of the given kind should be
// replicated based on this trust act.
func (ta *TrustAct) ShouldReplicate(kind uint16) bool {
	// Directory events are always replicated
	if IsDirectoryEventKind(kind) {
		return true
	}

	// Check if kind is in the replication list
	return ta.HasReplicationKind(kind)
}

// ShouldReplicateEvent determines whether a specific event should be replicated
// based on the trust level using partial replication (random dice-throw).
// This function uses crypto/rand for cryptographically secure randomness.
func (ta *TrustAct) ShouldReplicateEvent(kind uint16) (shouldReplicate bool, err error) {
	// Check if kind is eligible for replication
	if !ta.ShouldReplicate(kind) {
		return false, nil
	}

	// Trust level of 100 means always replicate
	if ta.TrustLevel == TrustLevelFull {
		return true, nil
	}

	// Trust level of 0 means never replicate
	if ta.TrustLevel == TrustLevelNone {
		return false, nil
	}

	// Generate cryptographically secure random number 0-100
	var randomBytes [1]byte
	if _, err = rand.Read(randomBytes[:]); chk.E(err) {
		return false, errorf.E("failed to generate random number: %w", err)
	}

	// Scale byte value (0-255) to 0-100 range
	randomValue := uint8((uint16(randomBytes[0]) * 101) / 256)

	// Replicate if random value is less than or equal to trust level
	shouldReplicate = randomValue <= uint8(ta.TrustLevel)
	return
}

// GetTargetPubkey returns the target relay's public key.
func (ta *TrustAct) GetTargetPubkey() string {
	return ta.TargetPubkey
}

// GetTrustLevel returns the trust level.
func (ta *TrustAct) GetTrustLevel() TrustLevel {
	return ta.TrustLevel
}

// GetRelayURL returns the target relay's URL.
func (ta *TrustAct) GetRelayURL() string {
	return ta.RelayURL
}

// GetExpiry returns the expiry time, or nil if no expiry is set.
func (ta *TrustAct) GetExpiry() *time.Time {
	return ta.Expiry
}

// GetReason returns the reason for the trust relationship.
func (ta *TrustAct) GetReason() TrustReason {
	return ta.Reason
}

// GetReplicationKinds returns the list of event kinds to replicate.
func (ta *TrustAct) GetReplicationKinds() []uint16 {
	return ta.ReplicationKinds
}

// GetIdentityTag returns the identity tag, or nil if not present.
func (ta *TrustAct) GetIdentityTag() *IdentityTag {
	return ta.IdentityTag
}
