package auth

import (
	"context"
	"time"

	"github.com/nexuscrm/kernel/pkg/auth"
	"github.com/nexuscrm/kernel/pkg/errors"
)

// BrowserSessionEstablishment is what a first-party browser login returns.
//
// It has no field that can carry a bearer, a refresh token, or any other value the
// page could read. That is the point of having a separate type rather than returning
// LoginResult: the browser lane's whole purpose is that the page holds no readable
// credential, and a handler cannot serialize what the type cannot express.
// ControlProof is the one secret here, and it exists only to be written into the
// HttpOnly cookie by the transport that owns that boundary.
type BrowserSessionEstablishment struct {
	ControlProof string `json:"-"`
	ExpiresAt    time.Time
	User         auth.UserSession
}

// EstablishBrowserSession authenticates a first-party browser login and returns a
// session whose only credential is the proof cookie.
//
// It delegates to Login rather than reimplementing authentication. That keeps one
// password-verification path, one lockout policy and one login-history record for
// every interactive login, browser or not — a second path here would be a second place
// for those to drift, and lockout drift is a real attack surface rather than a tidiness
// concern.
//
// SessionClass is declared explicitly rather than relying on the "everything empty"
// branch of IsInteractiveBrowserSession. Being precise about what that buys: the class
// is *not* a durable column — _System_Session has no such field, so it reaches the JWT
// claims and nowhere else, and the browser lane never reads it. What it does is make the
// control-proof opt-in legal on its own terms instead of by the coincidence that scope,
// connected app and grant type all happen to be empty here.
//
// Those three staying empty is load-bearing for a different reason: ValidateBrowserSession
// copies them from the stored row, so anything set here would become part of every
// request the browser makes on this session. Empty Scope currently means full API
// access, which is what an interactive browser login already gets today; narrowing it
// belongs to the separate empty-scope step, not silently here.
//
// The bearer Login mints is discarded and never reaches this function's result. It is
// not yet true that the durable session is bearer-incapable — that is what the later
// step removing the browser bearer does — so this is a transport guarantee, not a
// storage one, and it is worth being precise about which.
func (s *AuthService) EstablishBrowserSession(
	ctx context.Context,
	email string,
	password string,
	ip string,
	userAgent string,
) (*BrowserSessionEstablishment, error) {
	if s == nil {
		return nil, errors.NewInternalError("browser session establishment is unavailable", nil)
	}

	result, err := s.Login(ctx, email, password, ip, userAgent, &SessionOptions{
		SessionClass:      OAuthSessionClassPlatformBrowser,
		IssueControlProof: true,
	})
	if err != nil {
		return nil, err
	}
	if result == nil {
		return nil, errors.NewInternalError("login returned no result", nil)
	}
	if result.ControlProof == "" {
		// The password was right, but no proof was issued — so the caller has an
		// authenticated session it cannot address. Failing is the only safe answer:
		// reporting success would leave the browser signed in with nothing to send,
		// and the session live with no way to reach or revoke it.
		return nil, errors.NewInternalError("login did not issue a session control proof", nil)
	}

	return &BrowserSessionEstablishment{
		ControlProof: result.ControlProof,
		ExpiresAt:    result.ExpiresAt,
		User:         result.User,
	}, nil
}
