package httpapi

import (
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
	ssfreceiver "github.com/easyai/easyai-ai-gateway/apps/api/internal/securityevents"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
	"github.com/google/uuid"
)

type securityEventConnectionRequest struct {
	TransmitterIssuer      string `json:"transmitter_issuer"`
	ManagementClientID     string `json:"management_client_id"`
	ManagementClientSecret string `json:"management_client_secret"`
}

type securityEventConnectionResponse struct {
	Connected  bool                       `json:"connected"`
	Connection ssfreceiver.ConnectionView `json:"connection"`
	TraceID    string                     `json:"traceId,omitempty"`
	AuditID    string                     `json:"auditId,omitempty"`
}

func (s *Server) getSecurityEventConnection(w http.ResponseWriter, r *http.Request) {
	ensureSecurityEventTraceID(w, r)
	w.Header().Set("Cache-Control", "no-store")
	manager := s.currentSecurityEventManager()
	if manager == nil {
		writeJSON(w, http.StatusOK, map[string]any{"connected": false, "lifecycleStatus": "disconnected", "prerequisites": s.securityEventPrerequisites()})
		return
	}
	connection, err := manager.Get(r.Context())
	if errors.Is(err, store.ErrSecurityEventConnectionNotFound) {
		writeJSON(w, http.StatusOK, map[string]any{"connected": false, "lifecycleStatus": "disconnected", "prerequisites": s.securityEventPrerequisites()})
		return
	}
	if err != nil {
		writeError(w, http.StatusServiceUnavailable, "security event connection state is unavailable", "security_event_state_unavailable")
		return
	}
	w.Header().Set("ETag", fmt.Sprintf(`W/"%d"`, connection.Version))
	writeJSON(w, http.StatusOK, map[string]any{"connected": true, "connection": connection, "prerequisites": s.securityEventPrerequisites()})
}

func (s *Server) putSecurityEventConnection(w http.ResponseWriter, r *http.Request) {
	traceID := ensureSecurityEventTraceID(w, r)
	manager := s.currentSecurityEventManager()
	if manager == nil {
		writeError(w, http.StatusConflict, "OIDC must be configured before connecting security events", "security_event_prerequisite_missing")
		return
	}
	idempotencyKey, ok := requiredConnectionIdempotencyKey(w, r)
	if !ok {
		return
	}
	var request securityEventConnectionRequest
	r.Body = http.MaxBytesReader(w, r.Body, 16*1024)
	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&request); err != nil || strings.TrimSpace(request.TransmitterIssuer) == "" {
		writeError(w, http.StatusBadRequest, "invalid security event connection request", "invalid_request")
		return
	}
	request.TransmitterIssuer = strings.TrimRight(strings.TrimSpace(request.TransmitterIssuer), "/")
	request.ManagementClientID = strings.TrimSpace(request.ManagementClientID)
	if (request.ManagementClientID == "") != (request.ManagementClientSecret == "") || len(request.ManagementClientID) > 200 || len(request.ManagementClientSecret) > 512 {
		writeError(w, http.StatusBadRequest, "machine Client ID and Secret must be provided together", "invalid_machine_credential")
		return
	}
	secret := []byte(request.ManagementClientSecret)
	defer clear(secret)
	secretDigest := sha256.Sum256(secret)
	requestHash := securityEventOperationHash("connect", request.TransmitterIssuer+"\x00"+request.ManagementClientID+"\x00"+fmt.Sprintf("%x", secretDigest[:]))
	s.securityEventManagementMu.Lock()
	defer s.securityEventManagementMu.Unlock()
	if s.replaySecurityEventOperation(w, r, "connect", idempotencyKey, requestHash) {
		return
	}
	current, currentErr := manager.Get(r.Context())
	switch {
	case currentErr == nil:
		if !matchConnectionVersion(w, r, current.Version) {
			return
		}
	case errors.Is(currentErr, store.ErrSecurityEventConnectionNotFound):
		current = ssfreceiver.ConnectionView{}
		if !matchConnectionVersion(w, r, 0) {
			return
		}
	default:
		writeError(w, http.StatusServiceUnavailable, "security event connection state is unavailable", "security_event_state_unavailable")
		return
	}
	auditID, ok := s.requireSecurityEventConnectionAudit(w, r, "connect", traceID, current)
	if !ok {
		return
	}
	connection, err := manager.Connect(r.Context(), request.TransmitterIssuer, request.ManagementClientID, secret, idempotencyKey)
	if err != nil {
		s.writeSecurityEventConnectionError(w, r, manager, "connect", traceID, err)
		return
	}
	s.writeSecurityEventOperation(w, r, "connect", idempotencyKey, requestHash, traceID, auditID, connection)
}

func (s *Server) verifySecurityEventConnection(w http.ResponseWriter, r *http.Request) {
	traceID := ensureSecurityEventTraceID(w, r)
	s.securityEventManagementMu.Lock()
	defer s.securityEventManagementMu.Unlock()
	manager := s.currentSecurityEventManager()
	idempotencyKey, requestHash, auditID, ok := s.beginSecurityEventOperation(w, r, manager, "verify", traceID)
	if !ok {
		return
	}
	connection, err := manager.Verify(r.Context())
	if err != nil {
		s.writeSecurityEventConnectionError(w, r, manager, "verify", traceID, err)
		return
	}
	s.writeSecurityEventOperation(w, r, "verify", idempotencyKey, requestHash, traceID, auditID, connection)
}

func (s *Server) rotateSecurityEventConnectionCredential(w http.ResponseWriter, r *http.Request) {
	traceID := ensureSecurityEventTraceID(w, r)
	s.securityEventManagementMu.Lock()
	defer s.securityEventManagementMu.Unlock()
	manager := s.currentSecurityEventManager()
	idempotencyKey, requestHash, auditID, ok := s.beginSecurityEventOperation(w, r, manager, "rotate", traceID)
	if !ok {
		return
	}
	connection, err := manager.RotateCredential(r.Context())
	if err != nil {
		s.writeSecurityEventConnectionError(w, r, manager, "rotate", traceID, err)
		return
	}
	s.writeSecurityEventOperation(w, r, "rotate", idempotencyKey, requestHash, traceID, auditID, connection)
}

func (s *Server) deleteSecurityEventConnection(w http.ResponseWriter, r *http.Request) {
	traceID := ensureSecurityEventTraceID(w, r)
	s.securityEventManagementMu.Lock()
	defer s.securityEventManagementMu.Unlock()
	manager := s.currentSecurityEventManager()
	idempotencyKey, requestHash, auditID, ok := s.beginSecurityEventOperation(w, r, manager, "disconnect", traceID)
	if !ok {
		return
	}
	connection, err := manager.Disconnect(r.Context())
	if err != nil {
		s.writeSecurityEventConnectionError(w, r, manager, "disconnect", traceID, err)
		return
	}
	s.writeSecurityEventOperation(w, r, "disconnect", idempotencyKey, requestHash, traceID, auditID, connection)
}

func (s *Server) securityEventPrerequisites() map[string]any {
	runtime := s.currentIdentityRuntime()
	if runtime == nil {
		return map[string]any{
			"oidcConfigured": false, "introspectionClientConfigured": false,
			"publicBaseUrlConfigured": false, "managementClientId": "", "credentialInputSupported": false,
		}
	}
	revision := runtime.Revision
	return map[string]any{
		"oidcConfigured":                revision.Issuer != "" && revision.TenantID != "",
		"introspectionClientConfigured": revision.MachineClientID != "" && revision.MachineCredentialRef != "",
		"publicBaseUrlConfigured":       revision.PublicBaseURL != "",
		"managementClientId":            revision.MachineClientID,
		"credentialInputSupported":      false,
	}
}

func requiredConnectionIdempotencyKey(w http.ResponseWriter, r *http.Request) (string, bool) {
	value := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
	if value == "" || len(value) > 255 {
		writeError(w, http.StatusBadRequest, "Idempotency-Key is required", "idempotency_key_required")
		return "", false
	}
	return value, true
}

func (s *Server) beginSecurityEventOperation(w http.ResponseWriter, r *http.Request, manager *ssfreceiver.ConnectionManager, operation, traceID string) (string, string, string, bool) {
	if manager == nil {
		writeError(w, http.StatusNotFound, "security event connection does not exist", "security_event_connection_not_found")
		return "", "", "", false
	}
	idempotencyKey, ok := requiredConnectionIdempotencyKey(w, r)
	if !ok {
		return "", "", "", false
	}
	requestHash := securityEventOperationHash(operation, normalizedConnectionETag(r.Header.Get("If-Match")))
	if s.replaySecurityEventOperation(w, r, operation, idempotencyKey, requestHash) {
		return "", "", "", false
	}
	connection, err := manager.Get(r.Context())
	if err != nil {
		s.writeSecurityEventConnectionError(w, r, manager, operation, traceID, err)
		return "", "", "", false
	}
	if !matchConnectionVersion(w, r, connection.Version) {
		return "", "", "", false
	}
	auditID, ok := s.requireSecurityEventConnectionAudit(w, r, operation, traceID, connection)
	if !ok {
		return "", "", "", false
	}
	return idempotencyKey, requestHash, auditID, true
}

func (s *Server) replaySecurityEventOperation(w http.ResponseWriter, r *http.Request, operation, idempotencyKey, requestHash string) bool {
	if s.store == nil {
		return false
	}
	record, err := s.store.SecurityEventConnectionIdempotency(r.Context(), operation, idempotencyKey)
	if errors.Is(err, store.ErrSecurityEventConnectionNotFound) {
		return false
	}
	if err != nil {
		writeError(w, http.StatusServiceUnavailable, "security event idempotency state is unavailable", "security_event_state_unavailable")
		return true
	}
	if record.RequestHash != requestHash {
		writeError(w, http.StatusConflict, "Idempotency-Key was already used for a different request", "idempotency_key_reused")
		return true
	}
	var payload securityEventConnectionResponse
	if json.Unmarshal(record.Response, &payload) != nil {
		writeError(w, http.StatusServiceUnavailable, "security event idempotency response is unavailable", "security_event_state_unavailable")
		return true
	}
	w.Header().Set("Cache-Control", "no-store")
	w.Header().Set("Idempotent-Replayed", "true")
	w.Header().Set("ETag", fmt.Sprintf(`W/"%d"`, payload.Connection.Version))
	if payload.AuditID != "" {
		w.Header().Set("X-Audit-Id", payload.AuditID)
	}
	writeJSON(w, http.StatusAccepted, payload)
	return true
}

func (s *Server) writeSecurityEventOperation(w http.ResponseWriter, r *http.Request, operation, idempotencyKey, requestHash, traceID, auditID string, connection ssfreceiver.ConnectionView) {
	if auditID != "" {
		w.Header().Set("X-Audit-Id", auditID)
	}
	payload := securityEventConnectionResponse{Connected: true, Connection: connection, TraceID: traceID, AuditID: auditID}
	encoded, _ := json.Marshal(payload)
	if s.store != nil {
		if err := s.store.RecordSecurityEventConnectionIdempotency(r.Context(), operation, idempotencyKey, requestHash, encoded); err != nil && s.logger != nil {
			s.logger.Error("security event idempotency result could not be recorded", "error_category", "idempotency_store_failed", "operation", operation)
		}
	}
	w.Header().Set("Cache-Control", "no-store")
	w.Header().Set("ETag", fmt.Sprintf(`W/"%d"`, connection.Version))
	writeJSON(w, http.StatusAccepted, payload)
}

func securityEventOperationHash(operation, canonicalRequest string) string {
	digest := sha256.Sum256([]byte(operation + "\x00" + canonicalRequest))
	return fmt.Sprintf("%x", digest[:])
}

func normalizedConnectionETag(value string) string {
	value = strings.TrimSpace(value)
	value = strings.TrimPrefix(value, "W/")
	return strings.Trim(value, `"`)
}

func matchConnectionVersion(w http.ResponseWriter, r *http.Request, expected int64) bool {
	value := strings.TrimSpace(r.Header.Get("If-Match"))
	value = strings.TrimPrefix(value, "W/")
	value = strings.Trim(value, `"`)
	version, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		writeError(w, http.StatusPreconditionRequired, "If-Match is required", "if_match_required")
		return false
	}
	if version != expected {
		writeError(w, http.StatusPreconditionFailed, "security event connection version changed", "version_conflict")
		return false
	}
	return true
}

func (s *Server) writeSecurityEventConnectionError(w http.ResponseWriter, r *http.Request, manager *ssfreceiver.ConnectionManager, operation, traceID string, err error) {
	status, message, code := securityEventConnectionErrorProjection(err)
	connection := ssfreceiver.ConnectionView{}
	if manager != nil {
		connection, _ = manager.Get(r.Context())
	}
	errorCategory := code
	if connection.LastErrorCategory != nil && *connection.LastErrorCategory != "" {
		errorCategory = *connection.LastErrorCategory
	}
	if auditID := s.recordSecurityEventConnectionAudit(r, operation, "failure", errorCategory, traceID, connection); auditID != "" {
		w.Header().Set("X-Audit-Id", auditID)
	}
	writeError(w, status, message, code)
}

func securityEventConnectionErrorProjection(err error) (int, string, string) {
	switch {
	case errors.Is(err, store.ErrSecurityEventConnectionNotFound):
		return http.StatusNotFound, "security event connection does not exist", "security_event_connection_not_found"
	case errors.Is(err, store.ErrSecurityEventConnectionConflict):
		return http.StatusConflict, "security event connection conflicts with current state", "security_event_connection_conflict"
	case strings.Contains(err.Error(), "configured"), strings.Contains(err.Error(), "invalid"), strings.Contains(err.Error(), "HTTPS"):
		return http.StatusConflict, "security event connection prerequisites are incomplete", "security_event_prerequisite_missing"
	default:
		return http.StatusBadGateway, "authentication center security event service is unavailable", "security_event_transmitter_unavailable"
	}
}

func ensureSecurityEventTraceID(w http.ResponseWriter, r *http.Request) string {
	traceID := strings.TrimSpace(r.Header.Get("X-Trace-Id"))
	if !validSecurityEventDiagnosticID(traceID) {
		traceID = uuid.NewString()
	}
	w.Header().Set("X-Trace-Id", traceID)
	return traceID
}

func validSecurityEventDiagnosticID(value string) bool {
	if len(value) < 8 || len(value) > 128 {
		return false
	}
	for _, character := range value {
		if character >= 'a' && character <= 'z' || character >= 'A' && character <= 'Z' ||
			character >= '0' && character <= '9' || strings.ContainsRune("-_.", character) {
			continue
		}
		return false
	}
	return true
}

func (s *Server) recordSecurityEventConnectionAudit(r *http.Request, operation, outcome, errorCategory, traceID string, connection ssfreceiver.ConnectionView) string {
	if s.store == nil {
		return ""
	}
	actor, _ := auth.UserFromContext(r.Context())
	input := securityEventConnectionAuditInput(r, actor, operation, outcome, errorCategory, traceID, connection)
	audit, err := s.store.RecordAuditLog(r.Context(), input)
	if err != nil {
		if s.logger != nil {
			s.logger.WarnContext(r.Context(), "record security event connection audit failed", "operation", operation, "outcome", outcome, "error_category", "audit_store_failed", "trace_id", traceID)
		}
		return ""
	}
	return audit.ID
}

func (s *Server) requireSecurityEventConnectionAudit(w http.ResponseWriter, r *http.Request, operation, traceID string, connection ssfreceiver.ConnectionView) (string, bool) {
	auditID := s.recordSecurityEventConnectionAudit(r, operation, "requested", "", traceID, connection)
	if auditID == "" {
		writeError(w, http.StatusServiceUnavailable, "security event audit is unavailable; operation was not executed", "security_event_audit_unavailable")
		return "", false
	}
	w.Header().Set("X-Audit-Id", auditID)
	return auditID, true
}

func securityEventConnectionAuditInput(r *http.Request, actor *auth.User, operation, outcome, errorCategory, traceID string, connection ssfreceiver.ConnectionView) store.AuditLogInput {
	input := store.AuditLogInput{
		Category: "identity", Action: "identity.security_event_connection." + operation,
		TargetType: "security_event_connection", TargetID: firstNonEmptyText(connection.ConnectionID, "singleton"),
		RequestIP: limitAuditText(requestIP(r), 128), UserAgent: limitAuditText(r.UserAgent(), 512),
		AfterState: map[string]any{"lifecycleStatus": connection.LifecycleStatus, "healthMode": connection.HealthMode},
		Metadata:   map[string]any{"outcome": outcome, "errorCategory": errorCategory, "traceId": traceID},
	}
	if actor != nil {
		input.ActorGatewayUserID = uuidText(firstNonEmptyText(actor.GatewayUserID, actor.ID))
		input.ActorUserID, input.ActorUsername, input.ActorSource = actor.ID, actor.Username, actor.Source
		input.ActorRoles = actor.Roles
	}
	return input
}
