package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"lol.mleku.dev/chk"
	"lol.mleku.dev/log"
	"next.orly.dev/pkg/acl"
	"next.orly.dev/pkg/encoders/envelopes/authenvelope"
	"next.orly.dev/pkg/encoders/envelopes/eventenvelope"
	"next.orly.dev/pkg/encoders/envelopes/okenvelope"
	"next.orly.dev/pkg/encoders/hex"
	"next.orly.dev/pkg/encoders/kind"
	"next.orly.dev/pkg/encoders/reason"
	"next.orly.dev/pkg/utils"
)

func (l *Listener) HandleEvent(msg []byte) (err error) {
	log.D.F("handling event: %s", msg)
	// decode the envelope
	env := eventenvelope.NewSubmission()
	log.I.F("HandleEvent: received event message length: %d", len(msg))
	if msg, err = env.Unmarshal(msg); chk.E(err) {
		log.E.F("HandleEvent: failed to unmarshal event: %v", err)
		return
	}
	log.I.F(
		"HandleEvent: successfully unmarshaled event, kind: %d, pubkey: %s",
		env.E.Kind, hex.Enc(env.E.Pubkey),
	)
	defer func() {
		if env != nil && env.E != nil {
			env.E.Free()
		}
	}()

	log.I.F("HandleEvent: continuing with event processing...")
	if len(msg) > 0 {
		log.I.F("extra '%s'", msg)
	}

	// Check if sprocket is enabled and process event through it
	if l.sprocketManager != nil && l.sprocketManager.IsEnabled() {
		if l.sprocketManager.IsDisabled() {
			// Sprocket is disabled due to failure - reject all events
			log.W.F("sprocket is disabled, rejecting event %0x", env.E.ID)
			if err = Ok.Error(
				l, env,
				"sprocket disabled - events rejected until sprocket is restored",
			); chk.E(err) {
				return
			}
			return
		}

		if !l.sprocketManager.IsRunning() {
			// Sprocket is enabled but not running - reject all events
			log.W.F(
				"sprocket is enabled but not running, rejecting event %0x",
				env.E.ID,
			)
			if err = Ok.Error(
				l, env,
				"sprocket not running - events rejected until sprocket starts",
			); chk.E(err) {
				return
			}
			return
		}

		// Process event through sprocket
		response, sprocketErr := l.sprocketManager.ProcessEvent(env.E)
		if chk.E(sprocketErr) {
			log.E.F("sprocket processing failed: %v", sprocketErr)
			if err = Ok.Error(
				l, env, "sprocket processing failed",
			); chk.E(err) {
				return
			}
			return
		}

		// Handle sprocket response
		switch response.Action {
		case "accept":
			// Continue with normal processing
			log.D.F("sprocket accepted event %0x", env.E.ID)
		case "reject":
			// Return OK false with message
			if err = okenvelope.NewFrom(
				env.Id(), false,
				reason.Error.F(response.Msg),
			).Write(l); chk.E(err) {
				return
			}
			return
		case "shadowReject":
			// Return OK true but abort processing
			if err = Ok.Ok(l, env, ""); chk.E(err) {
				return
			}
			log.D.F("sprocket shadow rejected event %0x", env.E.ID)
			return
		default:
			log.W.F("unknown sprocket action: %s", response.Action)
			// Default to accept for unknown actions
		}
	}

	// Check if policy is enabled and process event through it
	if l.policyManager != nil && l.policyManager.Manager != nil && l.policyManager.Manager.IsEnabled() {

		// Check policy for write access
		allowed, policyErr := l.policyManager.CheckPolicy("write", env.E, l.authedPubkey.Load(), l.remote)
		if chk.E(policyErr) {
			log.E.F("policy check failed: %v", policyErr)
			if err = Ok.Error(
				l, env, "policy check failed",
			); chk.E(err) {
				return
			}
			return
		}

		if !allowed {
			log.D.F("policy rejected event %0x", env.E.ID)
			if err = Ok.Blocked(
				l, env, "event blocked by policy",
			); chk.E(err) {
				return
			}
			return
		}

		log.D.F("policy allowed event %0x", env.E.ID)

		// Check ACL policy for managed ACL mode
		if acl.Registry.Active.Load() == "managed" {
			allowed, aclErr := acl.Registry.CheckPolicy(env.E)
			if chk.E(aclErr) {
				log.E.F("ACL policy check failed: %v", aclErr)
				if err = Ok.Error(
					l, env, "ACL policy check failed",
				); chk.E(err) {
					return
				}
				return
			}

			if !allowed {
				log.D.F("ACL policy rejected event %0x", env.E.ID)
				if err = Ok.Blocked(
					l, env, "event blocked by ACL policy",
				); chk.E(err) {
					return
				}
				return
			}

			log.D.F("ACL policy allowed event %0x", env.E.ID)
		}
	}

	// check the event ID is correct
	calculatedId := env.E.GetIDBytes()
	if !utils.FastEqual(calculatedId, env.E.ID) {
		if err = Ok.Invalid(
			l, env, "event id is computed incorrectly, "+
				"event has ID %0x, but when computed it is %0x",
			env.E.ID, calculatedId,
		); chk.E(err) {
			return
		}
		return
	}
	// verify the signature
	var ok bool
	if ok, err = env.Verify(); chk.T(err) {
		if err = Ok.Error(
			l, env, fmt.Sprintf(
				"failed to verify signature: %s",
				err.Error(),
			),
		); chk.E(err) {
			return
		}
	} else if !ok {
		if err = Ok.Invalid(
			l, env,
			"signature is invalid",
		); chk.E(err) {
			return
		}
		return
	}
	// check permissions of user
	log.I.F(
		"HandleEvent: checking ACL permissions for pubkey: %s",
		hex.Enc(l.authedPubkey.Load()),
	)

	// If ACL mode is "none" and no pubkey is set, use the event's pubkey
	// But if auth is required or AuthToWrite is enabled, always use the authenticated pubkey
	var pubkeyForACL []byte
	if len(l.authedPubkey.Load()) == 0 && acl.Registry.Active.Load() == "none" && !l.Config.AuthRequired && !l.Config.AuthToWrite {
		pubkeyForACL = env.E.Pubkey
		log.I.F(
			"HandleEvent: ACL mode is 'none' and auth not required, using event pubkey for ACL check: %s",
			hex.Enc(pubkeyForACL),
		)
	} else {
		pubkeyForACL = l.authedPubkey.Load()
	}

	// If auth is required or AuthToWrite is enabled but user is not authenticated, deny access
	if (l.Config.AuthRequired || l.Config.AuthToWrite) && len(l.authedPubkey.Load()) == 0 {
		log.D.F("HandleEvent: authentication required for write operations but user not authenticated")
		if err = okenvelope.NewFrom(
			env.Id(), false,
			reason.AuthRequired.F("authentication required for write operations"),
		).Write(l); chk.E(err) {
			return
		}
		return
	}

	accessLevel := acl.Registry.GetAccessLevel(pubkeyForACL, l.remote)
	log.I.F("HandleEvent: ACL access level: %s", accessLevel)

	// Skip ACL check for admin/owner delete events
	skipACLCheck := false
	if env.E.Kind == kind.EventDeletion.K {
		// Check if the delete event signer is admin or owner
		for _, admin := range l.Admins {
			if utils.FastEqual(admin, env.E.Pubkey) {
				skipACLCheck = true
				log.I.F("HandleEvent: admin delete event - skipping ACL check")
				break
			}
		}
		if !skipACLCheck {
			for _, owner := range l.Owners {
				if utils.FastEqual(owner, env.E.Pubkey) {
					skipACLCheck = true
					log.I.F("HandleEvent: owner delete event - skipping ACL check")
					break
				}
			}
		}
	}

	if !skipACLCheck {
		switch accessLevel {
		case "none":
			log.D.F(
				"handle event: sending 'OK,false,auth-required...' to %s",
				l.remote,
			)
			if err = okenvelope.NewFrom(
				env.Id(), false,
				reason.AuthRequired.F("auth required for write access"),
			).Write(l); chk.E(err) {
				// return
			}
			log.D.F("handle event: sending challenge to %s", l.remote)
			if err = authenvelope.NewChallengeWith(l.challenge.Load()).
				Write(l); chk.E(err) {
				return
			}
			return
		case "read":
			log.D.F(
				"handle event: sending 'OK,false,auth-required:...' to %s",
				l.remote,
			)
			if err = okenvelope.NewFrom(
				env.Id(), false,
				reason.AuthRequired.F("auth required for write access"),
			).Write(l); chk.E(err) {
				return
			}
			log.D.F("handle event: sending challenge to %s", l.remote)
			if err = authenvelope.NewChallengeWith(l.challenge.Load()).
				Write(l); chk.E(err) {
				return
			}
			return
		case "blocked":
			log.D.F(
				"handle event: sending 'OK,false,blocked...' to %s",
				l.remote,
			)
			if err = okenvelope.NewFrom(
				env.Id(), false,
				reason.AuthRequired.F("IP address blocked"),
			).Write(l); chk.E(err) {
				return
			}
			return
		case "banned":
			log.D.F(
				"handle event: sending 'OK,false,banned...' to %s",
				l.remote,
			)
			if err = okenvelope.NewFrom(
				env.Id(), false,
				reason.AuthRequired.F("pubkey banned"),
			).Write(l); chk.E(err) {
				return
			}
			return
		default:
			// user has write access or better, continue
			log.I.F("HandleEvent: user has %s access, continuing", accessLevel)
		}
	} else {
		log.I.F("HandleEvent: skipping ACL check for admin/owner delete event")
	}

	// check if event is ephemeral - if so, deliver and return early
	if kind.IsEphemeral(env.E.Kind) {
		log.D.F("handling ephemeral event %0x (kind %d)", env.E.ID, env.E.Kind)
		// Send OK response for ephemeral events
		if err = Ok.Ok(l, env, ""); chk.E(err) {
			return
		}
		// Deliver the event to subscribers immediately
		clonedEvent := env.E.Clone()
		go l.publishers.Deliver(clonedEvent)
		log.D.F("delivered ephemeral event %0x", env.E.ID)
		return
	}

	// check for protected tag (NIP-70)
	protectedTag := env.E.Tags.GetFirst([]byte("-"))
	if protectedTag != nil && acl.Registry.Active.Load() != "none" {
		// check that the pubkey of the event matches the authed pubkey
		if !utils.FastEqual(l.authedPubkey.Load(), env.E.Pubkey) {
			if err = Ok.Blocked(
				l, env,
				"protected tag may only be published by user authed to the same pubkey",
			); chk.E(err) {
				return
			}
			return
		}
	}
	// if the event is a delete, process the delete
	log.I.F(
		"HandleEvent: checking if event is delete - kind: %d, EventDeletion.K: %d",
		env.E.Kind, kind.EventDeletion.K,
	)
	if env.E.Kind == kind.EventDeletion.K {
		log.I.F("processing delete event %0x", env.E.ID)

		// Store the delete event itself FIRST to ensure it's available for queries
		saveCtx, cancel := context.WithTimeout(
			context.Background(), 30*time.Second,
		)
		defer cancel()
		log.I.F(
			"attempting to save delete event %0x from pubkey %0x", env.E.ID,
			env.E.Pubkey,
		)
		log.I.F("delete event pubkey hex: %s", hex.Enc(env.E.Pubkey))
		if _, err = l.SaveEvent(saveCtx, env.E); err != nil {
			log.E.F("failed to save delete event %0x: %v", env.E.ID, err)
			if strings.HasPrefix(err.Error(), "blocked:") {
				errStr := err.Error()[len("blocked: "):len(err.Error())]
				if err = Ok.Error(
					l, env, errStr,
				); chk.E(err) {
					return
				}
				return
			}
			chk.E(err)
			return
		}
		log.I.F("successfully saved delete event %0x", env.E.ID)

		// Now process the deletion (remove target events)
		if err = l.HandleDelete(env); err != nil {
			log.E.F("HandleDelete failed for event %0x: %v", env.E.ID, err)
			if strings.HasPrefix(err.Error(), "blocked:") {
				errStr := err.Error()[len("blocked: "):len(err.Error())]
				if err = Ok.Error(
					l, env, errStr,
				); chk.E(err) {
					return
				}
				return
			}
			// For non-blocked errors, still send OK but log the error
			log.W.F("Delete processing failed but continuing: %v", err)
		} else {
			log.I.F(
				"HandleDelete completed successfully for event %0x", env.E.ID,
			)
		}

		// Send OK response for delete events
		if err = Ok.Ok(l, env, ""); chk.E(err) {
			return
		}

		// Deliver the delete event to subscribers
		clonedEvent := env.E.Clone()
		go l.publishers.Deliver(clonedEvent)
		log.D.F("processed delete event %0x", env.E.ID)
		return
	} else {
		// check if the event was deleted
		// Combine admins and owners for deletion checking
		adminOwners := append(l.Admins, l.Owners...)
		if err = l.CheckForDeleted(env.E, adminOwners); err != nil {
			if strings.HasPrefix(err.Error(), "blocked:") {
				errStr := err.Error()[len("blocked: "):len(err.Error())]
				if err = Ok.Error(
					l, env, errStr,
				); chk.E(err) {
					return
				}
			}
		}
	}
	// store the event - use a separate context to prevent cancellation issues
	saveCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	// log.I.F("saving event %0x, %s", env.E.ID, env.E.Serialize())
	if _, err = l.SaveEvent(saveCtx, env.E); err != nil {
		if strings.HasPrefix(err.Error(), "blocked:") {
			errStr := err.Error()[len("blocked: "):len(err.Error())]
			if err = Ok.Error(
				l, env, errStr,
			); chk.E(err) {
				return
			}
			return
		}
		chk.E(err)
		return
	}
	// Send a success response storing
	if err = Ok.Ok(l, env, ""); chk.E(err) {
		return
	}
	// Deliver the event to subscribers immediately after sending OK response
	// Clone the event to prevent corruption when the original is freed
	clonedEvent := env.E.Clone()
	go l.publishers.Deliver(clonedEvent)
	log.D.F("saved event %0x", env.E.ID)
	var isNewFromAdmin bool
	// Check if event is from admin or owner
	for _, admin := range l.Admins {
		if utils.FastEqual(admin, env.E.Pubkey) {
			isNewFromAdmin = true
			break
		}
	}
	if !isNewFromAdmin {
		for _, owner := range l.Owners {
			if utils.FastEqual(owner, env.E.Pubkey) {
				isNewFromAdmin = true
				break
			}
		}
	}
	if isNewFromAdmin {
		log.I.F("new event from admin %0x", env.E.Pubkey)
		// if a follow list was saved, reconfigure ACLs now that it is persisted
		if env.E.Kind == kind.FollowList.K ||
			env.E.Kind == kind.RelayListMetadata.K {
			// Run ACL reconfiguration asynchronously to prevent blocking websocket operations
			go func() {
				if err := acl.Registry.Configure(); chk.E(err) {
					log.E.F("failed to reconfigure ACL: %v", err)
				}
			}()
		}
	}
	return
}
