package securityevents

import (
	"context"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/json"
	"errors"
	"io"
	"mime"
	"net/http"
	"strings"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

type Repository interface {
	ApplySessionRevoked(context.Context, store.ApplySessionRevokedInput) (store.ApplySecurityEventResult, error)
	ApplySecurityEventStreamUpdated(context.Context, string, string, string, string, string, string, time.Time) (bool, error)
	ConfirmSecurityEventVerification(context.Context, string, string, string, string, []byte, time.Time) (bool, error)
	RecordSecurityEventReceipt(context.Context, string, string, string, string, string) (bool, error)
}

type Handler struct {
	verifier     *Verifier
	repository   Repository
	issuer       string
	audience     string
	streamID     string
	bearerSecret string
	nextSecret   string
	clock        func() time.Time
	metrics      *Metrics
}

func (h *Handler) SetMetrics(metrics *Metrics) { h.metrics = metrics }

func NewHandler(verifier *Verifier, repository Repository, issuer, audience, streamID, bearerSecret, nextSecret string) (*Handler, error) {
	if verifier == nil || repository == nil || issuer == "" || audience == "" || streamID == "" || len(bearerSecret) < 16 {
		return nil, errors.New("SSF receiver dependencies and bearer secret are required")
	}
	return &Handler{verifier: verifier, repository: repository, issuer: issuer, audience: audience, streamID: streamID, bearerSecret: bearerSecret, nextSecret: nextSecret, clock: time.Now}, nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	started := h.clock()
	outcome := "rejected"
	var sessionsDeleted int64
	defer func() {
		if h.metrics != nil {
			h.metrics.ObserveReceipt(outcome, h.clock().Sub(started), sessionsDeleted)
		}
	}()
	w.Header().Set("Cache-Control", "no-store")
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	if !h.validAuthorization(r.Header.Get("Authorization")) {
		w.Header().Set("WWW-Authenticate", `Bearer realm="ssf-receiver"`)
		writeSETError(w, http.StatusUnauthorized, "authentication_failed")
		return
	}
	mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
	if err != nil || mediaType != "application/secevent+jwt" || r.Header.Get("Content-Encoding") != "" {
		writeSETError(w, http.StatusBadRequest, "invalid_request")
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 64*1024)
	payload, err := io.ReadAll(r.Body)
	if err != nil || len(payload) == 0 {
		writeSETError(w, http.StatusBadRequest, "invalid_request")
		return
	}
	event, err := h.verifier.Verify(r.Context(), string(payload))
	if err != nil {
		var protocol *protocolError
		if errors.As(err, &protocol) {
			writeSETError(w, http.StatusBadRequest, protocol.Code)
		} else {
			writeSETError(w, http.StatusBadRequest, "invalid_request")
		}
		return
	}
	switch event.EventType {
	case SessionRevokedEventType:
		var result store.ApplySecurityEventResult
		result, err = h.repository.ApplySessionRevoked(r.Context(), store.ApplySessionRevokedInput{
			Issuer: event.Issuer, Audience: event.Audience, JTI: event.JTI, TransactionID: event.TransactionID,
			SubjectIssuer: event.SubjectIssuer, ApplicationID: event.ApplicationID, SubjectType: event.SubjectType,
			TenantID: event.TenantID, Subject: event.Subject, EventTimestamp: event.EventTimestamp,
			InitiatingEntity: event.InitiatingEntity,
		})
		if err == nil {
			sessionsDeleted = result.SessionsDeleted
			if result.Duplicate {
				outcome = "duplicate"
			} else {
				outcome = "accepted"
			}
		}
	case VerificationEventType:
		var confirmed bool
		if event.State == "" {
			confirmed, err = h.repository.RecordSecurityEventReceipt(r.Context(), h.issuer, h.audience, event.JTI, VerificationEventType, h.streamID)
		} else {
			stateHash := sha256.Sum256([]byte(event.State))
			confirmed, err = h.repository.ConfirmSecurityEventVerification(r.Context(), h.issuer, h.audience, h.streamID, event.JTI, stateHash[:], h.clock().UTC())
		}
		if err != nil && strings.Contains(err.Error(), "invalid verification state") {
			writeSETError(w, http.StatusBadRequest, "invalid_request")
			return
		}
		if err == nil {
			if confirmed {
				outcome = "accepted"
				if h.metrics != nil {
					h.metrics.ObserveVerificationAccepted()
				}
			} else {
				outcome = "duplicate"
			}
		}
	case StreamUpdatedEventType:
		var inserted bool
		inserted, err = h.repository.ApplySecurityEventStreamUpdated(
			r.Context(), h.issuer, h.audience, event.JTI, h.streamID, event.StreamStatus, event.Reason, h.clock().UTC(),
		)
		if err == nil {
			if inserted {
				outcome = "accepted"
			} else {
				outcome = "duplicate"
			}
		}
	}
	if err != nil {
		w.Header().Set("Retry-After", "1")
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}
	w.WriteHeader(http.StatusAccepted)
}

func (h *Handler) validAuthorization(value string) bool {
	if constantBearerCompare(value, h.bearerSecret) {
		return true
	}
	return h.nextSecret != "" && constantBearerCompare(value, h.nextSecret)
}

func constantBearerCompare(header, secret string) bool {
	providedHash := sha256.Sum256([]byte(header))
	expectedHash := sha256.Sum256([]byte("Bearer " + secret))
	return subtle.ConstantTimeCompare(providedHash[:], expectedHash[:]) == 1
}

func writeSETError(w http.ResponseWriter, status int, code string) {
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Language", "en")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(map[string]string{
		"err":         code,
		"description": setErrorDescription(code),
	})
}

func setErrorDescription(code string) string {
	switch code {
	case "invalid_request":
		return "The SET request is malformed or contains invalid claims."
	case "invalid_key":
		return "The SET signing key or signature is invalid."
	case "invalid_issuer":
		return "The SET issuer is not accepted by this receiver."
	case "invalid_audience":
		return "The SET audience does not identify this receiver."
	case "authentication_failed":
		return "Receiver authentication failed."
	case "access_denied":
		return "The SET is not permitted for this receiver."
	default:
		return "The SET request could not be processed."
	}
}
