package securityevents

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

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

type StateRepository interface {
	EnsureSecurityEventStreamState(context.Context, string, string, string) error
	BeginSecurityEventVerification(context.Context, string, string, []byte, time.Time) error
	RecordSecurityEventHeartbeatFailure(context.Context, string, string, string) error
	AdvanceSecurityEventStreamState(context.Context, string, string, time.Time, time.Duration) error
	EvaluateOIDCSecurityEvent(context.Context, string, string, string, string, string, string, time.Time, time.Time, time.Duration) (store.SecurityEventEvaluation, error)
}

type ServiceConfig struct {
	Issuer             string
	Audience           string
	StreamID           string
	ManagementTokenURL string
	ManagementClientID string
	ManagementSecret   string
	HeartbeatInterval  time.Duration
	StaleAfter         time.Duration
	HTTPClient         *http.Client
}

type Service struct {
	repository StateRepository
	config     ServiceConfig
	client     *http.Client
	clock      func() time.Time
	tokenMu    sync.Mutex
	token      string
	tokenUntil time.Time
	metrics    *Metrics
}

type heartbeatLeadershipRepository interface {
	TryAcquireSecurityEventHeartbeatLeadership(context.Context) (store.Leadership, bool, error)
}

const (
	heartbeatLeadershipRetryInterval = 5 * time.Second
	heartbeatLeadershipKeepAlive     = 5 * time.Second
	heartbeatExecutionTimeout        = 30 * time.Second
)

func (s *Service) SetMetrics(metrics *Metrics) { s.metrics = metrics }

func NewService(repository StateRepository, config ServiceConfig) (*Service, error) {
	if repository == nil || config.Issuer == "" || config.Audience == "" || config.StreamID == "" ||
		config.ManagementTokenURL == "" || config.ManagementClientID == "" || config.ManagementSecret == "" {
		return nil, errors.New("SSF heartbeat configuration is incomplete")
	}
	if config.HeartbeatInterval <= 0 || config.StaleAfter < 2*config.HeartbeatInterval {
		return nil, errors.New("SSF heartbeat timing is invalid")
	}
	client := config.HTTPClient
	if client == nil {
		transport := http.DefaultTransport.(*http.Transport).Clone()
		transport.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12}
		transport.DisableCompression = true
		client = &http.Client{Timeout: 10 * time.Second,
			Transport:     transport,
			CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }}
	}
	return &Service{repository: repository, config: config, client: client, clock: time.Now}, nil
}

func (s *Service) Start(ctx context.Context) error {
	if err := s.Initialize(ctx); err != nil {
		return err
	}
	go s.Run(ctx)
	return nil
}

func (s *Service) Initialize(ctx context.Context) error {
	return s.repository.EnsureSecurityEventStreamState(ctx, s.config.Issuer, s.config.Audience, s.config.StreamID)
}

func (s *Service) Run(ctx context.Context) { s.run(ctx) }

// RequestVerification asks for an immediate verification without changing the
// remote stream status. In particular, this never re-enables a stream paused
// by an authentication-center administrator.
func (s *Service) RequestVerification(ctx context.Context) { s.sendVerification(ctx) }

func (s *Service) Evaluate(ctx context.Context, identity auth.OIDCSecurityEventIdentity) (auth.OIDCSecurityEventEvaluation, error) {
	result, err := s.repository.EvaluateOIDCSecurityEvent(
		ctx, s.config.Issuer, s.config.Audience, identity.Issuer, identity.ApplicationID, identity.TenantID, identity.Subject,
		identity.IssuedAt, s.clock().UTC(), s.config.StaleAfter,
	)
	if err != nil {
		return auth.OIDCSecurityEventEvaluation{}, err
	}
	if result.Revoked && s.metrics != nil {
		s.metrics.ObserveWatermarkRejection()
	}
	return auth.OIDCSecurityEventEvaluation{Revoked: result.Revoked, RequireIntrospection: result.RequireIntrospection}, nil
}

func (s *Service) run(ctx context.Context) {
	elector, distributed := s.repository.(heartbeatLeadershipRepository)
	if !distributed {
		s.runHeartbeatLoop(ctx)
		return
	}
	retry := time.NewTicker(heartbeatLeadershipRetryInterval)
	defer retry.Stop()
	for {
		acquireCtx, cancel := context.WithTimeout(ctx, heartbeatLeadershipKeepAlive)
		leadership, acquired, err := elector.TryAcquireSecurityEventHeartbeatLeadership(acquireCtx)
		cancel()
		if err != nil {
			_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "leader_election_failed")
		} else if acquired {
			if !s.runLeaderHeartbeatLoop(ctx, leadership) {
				return
			}
		}
		select {
		case <-ctx.Done():
			return
		case <-retry.C:
		}
	}
}

func (s *Service) runHeartbeatLoop(ctx context.Context) {
	s.sendScheduledVerification(ctx)
	ticker := time.NewTicker(s.config.HeartbeatInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			s.sendScheduledVerification(ctx)
		}
	}
}

func (s *Service) runLeaderHeartbeatLoop(ctx context.Context, leadership store.Leadership) bool {
	defer leadership.Release()
	s.sendScheduledVerification(ctx)
	heartbeat := time.NewTicker(s.config.HeartbeatInterval)
	defer heartbeat.Stop()
	keepalive := time.NewTicker(heartbeatLeadershipKeepAlive)
	defer keepalive.Stop()
	for {
		select {
		case <-ctx.Done():
			return false
		case <-heartbeat.C:
			s.sendScheduledVerification(ctx)
		case <-keepalive.C:
			keepaliveCtx, cancel := context.WithTimeout(ctx, heartbeatLeadershipKeepAlive)
			err := leadership.KeepAlive(keepaliveCtx)
			cancel()
			if err != nil {
				_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "leader_keepalive_failed")
				return true
			}
		}
	}
}

func (s *Service) sendScheduledVerification(ctx context.Context) {
	verificationCtx, cancel := context.WithTimeout(ctx, heartbeatExecutionTimeout)
	defer cancel()
	s.sendVerification(verificationCtx)
}

func (s *Service) sendVerification(ctx context.Context) {
	outcome := "failed"
	defer func() {
		if s.metrics != nil {
			s.metrics.ObserveHeartbeat(outcome)
		}
	}()
	if err := s.repository.AdvanceSecurityEventStreamState(ctx, s.config.Issuer, s.config.Audience, s.clock().UTC(), s.config.StaleAfter); err != nil {
		_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "state_advance_failed")
	}
	state, err := randomVerificationState()
	if err != nil {
		_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "state_generation_failed")
		return
	}
	hash := sha256.Sum256([]byte(state))
	now := s.clock().UTC()
	if err := s.repository.BeginSecurityEventVerification(ctx, s.config.Issuer, s.config.Audience, hash[:], now); err != nil {
		_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "state_persistence_failed")
		return
	}
	token, err := s.managementToken(ctx)
	if err != nil {
		_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "management_token_failed")
		return
	}
	payload, _ := json.Marshal(map[string]string{"stream_id": s.config.StreamID, "state": state})
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimRight(s.config.Issuer, "/")+"/v1/verify", bytes.NewReader(payload))
	if err != nil {
		return
	}
	request.Header.Set("Authorization", "Bearer "+token)
	request.Header.Set("Content-Type", "application/json")
	response, err := s.client.Do(request)
	if err != nil {
		_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, "verification_request_failed")
		return
	}
	defer response.Body.Close()
	_, _ = io.Copy(io.Discard, io.LimitReader(response.Body, 64*1024))
	if response.StatusCode != http.StatusNoContent {
		_ = s.repository.RecordSecurityEventHeartbeatFailure(ctx, s.config.Issuer, s.config.Audience, fmt.Sprintf("verification_http_%d", response.StatusCode))
		return
	}
	outcome = "accepted"
}

func (s *Service) managementToken(ctx context.Context) (string, error) {
	s.tokenMu.Lock()
	defer s.tokenMu.Unlock()
	if s.token != "" && s.clock().Before(s.tokenUntil) {
		return s.token, nil
	}
	form := url.Values{"grant_type": {"client_credentials"}, "scope": {"ssf.stream.read ssf.stream.verify"}}
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, s.config.ManagementTokenURL, strings.NewReader(form.Encode()))
	if err != nil {
		return "", err
	}
	request.SetBasicAuth(s.config.ManagementClientID, s.config.ManagementSecret)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	response, err := s.client.Do(request)
	if err != nil {
		return "", err
	}
	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		_, _ = io.Copy(io.Discard, io.LimitReader(response.Body, 64*1024))
		return "", fmt.Errorf("management token endpoint returned HTTP %d", response.StatusCode)
	}
	payload, err := io.ReadAll(io.LimitReader(response.Body, 64*1024+1))
	if err != nil || len(payload) > 64*1024 {
		return "", errors.New("management token response is too large")
	}
	var result struct {
		AccessToken string `json:"access_token"`
		TokenType   string `json:"token_type"`
		ExpiresIn   int64  `json:"expires_in"`
	}
	if json.Unmarshal(payload, &result) != nil || result.AccessToken == "" || !strings.EqualFold(result.TokenType, "Bearer") {
		return "", errors.New("management token response is invalid")
	}
	if result.ExpiresIn <= 0 {
		result.ExpiresIn = 60
	}
	cacheFor := time.Duration(result.ExpiresIn) * time.Second
	if cacheFor > 30*time.Second {
		cacheFor -= 30 * time.Second
	}
	s.token, s.tokenUntil = result.AccessToken, s.clock().Add(cacheFor)
	return s.token, nil
}

func randomVerificationState() (string, error) {
	payload := make([]byte, 32)
	if _, err := rand.Read(payload); err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(payload), nil
}
