//go:build integration

package integrationtest

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"time"

	"github.com/go-jose/go-jose/v4"
	"github.com/go-jose/go-jose/v4/jwt"

	"github.com/ultisuite/ulti-backend/internal/auth"
)

const testOIDCClientID = "ulti-backend-test"

// OIDCServer is a minimal OIDC issuer for integration tests.
type OIDCServer struct {
	URL      string
	Issuer   string
	ClientID string

	server *httptest.Server
	key    *rsa.PrivateKey
	jwk    jose.JSONWebKey
}

func NewOIDCServer() (*OIDCServer, error) {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, fmt.Errorf("generate rsa key: %w", err)
	}

	jwk := jose.JSONWebKey{Key: key.Public(), KeyID: "test-key", Use: "sig", Algorithm: string(jose.RS256)}
	s := &OIDCServer{
		ClientID: testOIDCClientID,
		key:      key,
		jwk:      jwk,
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
		_ = json.NewEncoder(w).Encode(map[string]string{
			"issuer":   s.Issuer,
			"jwks_uri": s.Issuer + "/jwks/",
		})
	})
	mux.HandleFunc("/jwks/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"keys": []jose.JSONWebKey{s.jwk}})
	})

	s.server = httptest.NewServer(mux)
	s.URL = strings.TrimSuffix(s.server.URL, "/")
	s.Issuer = s.URL
	return s, nil
}

func (s *OIDCServer) Verifier(ctx context.Context) (*auth.Verifier, error) {
	return auth.NewVerifier(ctx, s.URL, s.ClientID, "localhost")
}

func (s *OIDCServer) Holder(ctx context.Context) (*auth.Holder, error) {
	v, err := s.Verifier(ctx)
	if err != nil {
		return nil, err
	}
	return auth.NewHolder(v), nil
}

func (s *OIDCServer) IssueToken(claims *auth.Claims) (string, error) {
	if claims == nil {
		return "", fmt.Errorf("claims is nil")
	}
	now := time.Now()
	opts := (&jose.SignerOptions{}).WithHeader(jose.HeaderKey("kid"), s.jwk.KeyID)
	sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: s.key}, opts)
	if err != nil {
		return "", err
	}

	builder := jwt.Signed(sig).Claims(jwt.Claims{
		Issuer:   s.Issuer,
		Subject:  claims.Sub,
		Audience: jwt.Audience{s.ClientID},
		Expiry:   jwt.NewNumericDate(now.Add(time.Hour)),
		IssuedAt: jwt.NewNumericDate(now),
	}).Claims(map[string]any{
		"email":  claims.Email,
		"name":   claims.Name,
		"groups": claims.Groups,
	})
	return builder.Serialize()
}

func (s *OIDCServer) Close() {
	if s != nil && s.server != nil {
		s.server.Close()
	}
}
