package serverruntime

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"reflect"
	"strings"
	"testing"
	"time"

	featureauth "github.com/nexuscrm/auth"
	jwtauth "github.com/nexuscrm/kernel/pkg/auth"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/models"
)

type sessionControlAdapterUserRepo struct {
	ports.UserRepository
	user *models.User
	err  error
}

func (r *sessionControlAdapterUserRepo) GetUserByID(context.Context, string) (*models.User, error) {
	return r.user, r.err
}

type sessionControlAdapterSessionRepo struct {
	ports.SessionRepository
	session *models.SystemSession
	getErr  error
}

func (r *sessionControlAdapterSessionRepo) GetSession(context.Context, string) (*models.SystemSession, error) {
	return r.session, r.getErr
}

func (r *sessionControlAdapterSessionRepo) IsConnectedAppActive(context.Context, string) (bool, error) {
	return true, nil
}

func (r *sessionControlAdapterSessionRepo) RevokeSessionWithControlProof(context.Context, string, string) (bool, error) {
	return false, nil
}

func (r *sessionControlAdapterSessionRepo) RenewSessionWithControlProof(_ context.Context, sessionID, previousToken, expectedControlProofHash, token string, expiresAt, now time.Time) (bool, error) {
	if r.session == nil || r.session.ID != sessionID || r.session.Token != previousToken || r.session.ControlProofHash == nil || *r.session.ControlProofHash != expectedControlProofHash {
		return false, nil
	}
	r.session.Token = token
	r.session.ExpiresAt = expiresAt
	r.session.LastActivity = now
	return true, nil
}

func TestAPIHostSessionAuthenticatorValidateSessionWithControlMapsRenewal(t *testing.T) {
	issuedAt := time.Now().Add(-13 * time.Hour)
	roleID := "role-1"
	roleName := "Sales"
	userSession := jwtauth.UserSession{
		ID:                "user-1",
		Name:              "Ada Lovelace",
		Email:             "ada@example.test",
		ProfileId:         "profile-1",
		ProfileName:       "Standard User",
		RoleId:            &roleID,
		RoleName:          &roleName,
		ConnectedAppId:    "nexuscrm-ui",
		Scope:             "api",
		GrantType:         featureauth.OAuthGrantPassword,
		SessionClass:      featureauth.OAuthSessionClassPlatformBrowser,
		SystemPermissions: map[string]bool{"ManageUsers": true},
	}
	token, tokenClaims, err := jwtauth.GenerateTokenForSession(userSession, "session-1", issuedAt, jwtauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateTokenForSession: %v", err)
	}
	proof := base64.RawURLEncoding.EncodeToString([]byte("0123456789abcdef0123456789abcdef"))
	digest := sha256.Sum256([]byte(proof))
	proofHash := hex.EncodeToString(digest[:])
	scope, appID, grantType := userSession.Scope, userSession.ConnectedAppId, userSession.GrantType
	sessions := &sessionControlAdapterSessionRepo{session: &models.SystemSession{
		ID:               tokenClaims.ID,
		UserID:           userSession.ID,
		Token:            token,
		ExpiresAt:        tokenClaims.ExpiresAt.Time,
		LastActivity:     issuedAt,
		ControlProofHash: &proofHash,
		Scope:            &scope,
		ConnectedAppID:   &appID,
		GrantType:        &grantType,
	}}
	users := &sessionControlAdapterUserRepo{user: &models.User{ID: userSession.ID, IsActive: true}}
	adapter := apiHostSessionAuthenticator{svc: featureauth.NewAuthService(users, sessions, nil)}

	got, err := adapter.ValidateSessionWithControl(context.Background(), token, proof)
	if err != nil {
		t.Fatalf("ValidateSessionWithControl: %v", err)
	}
	if got.Claims == nil || got.Claims.SessionID != "session-1" {
		t.Fatalf("claims = %#v, want session-1", got.Claims)
	}
	if got.Claims.User.ID != userSession.ID || got.Claims.User.Name != userSession.Name || got.Claims.User.Email != userSession.Email ||
		got.Claims.User.ProfileID != userSession.ProfileId || got.Claims.User.ProfileName != userSession.ProfileName ||
		got.Claims.User.ConnectedAppID != userSession.ConnectedAppId || got.Claims.User.Scope != userSession.Scope ||
		got.Claims.User.GrantType != userSession.GrantType || got.Claims.User.SessionClass != userSession.SessionClass ||
		!reflect.DeepEqual(got.Claims.User.RoleID, userSession.RoleId) || !reflect.DeepEqual(got.Claims.User.RoleName, userSession.RoleName) ||
		!reflect.DeepEqual(got.Claims.User.SystemPermissions, userSession.SystemPermissions) {
		t.Fatalf("mapped user = %#v, want %#v", got.Claims.User, userSession)
	}
	if got.RefreshedBearerToken == "" || got.RefreshedBearerToken == token {
		t.Fatalf("refreshed bearer = %q, want a replacement", got.RefreshedBearerToken)
	}
	if got.ProofCookieExpiresAt.IsZero() || !got.ProofCookieExpiresAt.Equal(sessions.session.ExpiresAt) {
		t.Fatalf("proof cookie expiry = %v, durable expiry = %v", got.ProofCookieExpiresAt, sessions.session.ExpiresAt)
	}
}

func TestAPIHostSessionAuthenticatorValidateSessionWithControlFailsClosed(t *testing.T) {
	t.Run("nil service", func(t *testing.T) {
		got, err := (apiHostSessionAuthenticator{}).ValidateSessionWithControl(context.Background(), "bearer", "proof")
		if err == nil || !strings.Contains(err.Error(), "auth service is not configured") {
			t.Fatalf("ValidateSessionWithControl = %#v, %v; want configuration error", got, err)
		}
		if got.Claims != nil || got.RefreshedBearerToken != "" || !got.ProofCookieExpiresAt.IsZero() {
			t.Fatalf("ValidateSessionWithControl failure leaked a result: %#v", got)
		}
	})

	t.Run("auth service error", func(t *testing.T) {
		token, claims, err := jwtauth.GenerateTokenForSession(jwtauth.UserSession{ID: "user-1"}, "session-1", time.Now(), jwtauth.DefaultSessionLifetime)
		if err != nil {
			t.Fatalf("GenerateTokenForSession: %v", err)
		}
		sessions := &sessionControlAdapterSessionRepo{getErr: errors.New("session store unavailable")}
		users := &sessionControlAdapterUserRepo{user: &models.User{ID: claims.Subject, IsActive: true}}
		adapter := apiHostSessionAuthenticator{svc: featureauth.NewAuthService(users, sessions, nil)}

		got, err := adapter.ValidateSessionWithControl(context.Background(), token, "")
		if err == nil || !strings.Contains(err.Error(), "session store unavailable") {
			t.Fatalf("ValidateSessionWithControl = %#v, %v; want repository error", got, err)
		}
		if got.Claims != nil || got.RefreshedBearerToken != "" || !got.ProofCookieExpiresAt.IsZero() {
			t.Fatalf("ValidateSessionWithControl failure leaked a result: %#v", got)
		}
	})
}
