package security

import (
	"context"
	"net/http"
)

// UserContext holds authenticated user information
type UserContext struct {
	UserID           int            `json:"user_id"`
	UserName         string         `json:"user_name"`
	UserLevel        int            `json:"user_level"`
	SessionID        string         `json:"session_id"`
	SessionRID       int64          `json:"session_rid"`
	RemoteID         string         `json:"remote_id"`
	Roles            []string       `json:"roles"`
	Email            string         `json:"email"`
	Claims           map[string]any `json:"claims"`
	Meta             map[string]any `json:"meta"`               // Additional metadata that can hold any JSON-serializable values
	TwoFactorEnabled bool           `json:"two_factor_enabled"` // Indicates if 2FA is enabled for this user
	ProgramUserID    int            `json:"program_user_id"`
	ProgramUserTable string         `json:"program_user_table"`
}

// LoginRequest contains credentials for login
type LoginRequest struct {
	Username      string         `json:"username"`
	Password      string         `json:"password"`
	TwoFactorCode string         `json:"two_factor_code,omitempty"` // TOTP or backup code
	Claims        map[string]any `json:"claims"`                    // Additional login data
	Meta          map[string]any `json:"meta"`                      // Additional metadata to be set on user context
}

// RegisterRequest contains information for new user registration
type RegisterRequest struct {
	Username  string         `json:"username"`
	Password  string         `json:"password"`
	Email     string         `json:"email"`
	UserLevel int            `json:"user_level"`
	Roles     []string       `json:"roles"`
	Claims    map[string]any `json:"claims"` // Additional registration data
	Meta      map[string]any `json:"meta"`   // Additional metadata
}

// LoginResponse contains the result of a login attempt
type LoginResponse struct {
	Token              string           `json:"token"`
	RefreshToken       string           `json:"refresh_token"`
	User               *UserContext     `json:"user"`
	ExpiresIn          int64            `json:"expires_in"`                 // Token expiration in seconds
	Requires2FA        bool             `json:"requires_2fa"`               // True if 2FA code is required
	TwoFactorSetupData *TwoFactorSecret `json:"two_factor_setup,omitempty"` // Present when setting up 2FA
	Meta               map[string]any   `json:"meta"`                       // Additional metadata to be set on user context
}

// LogoutRequest contains information for logout
type LogoutRequest struct {
	Token  string `json:"token"`
	UserID int    `json:"user_id"`
}

// PasswordResetRequest initiates a password reset for a user
type PasswordResetRequest struct {
	Email    string `json:"email,omitempty"`
	Username string `json:"username,omitempty"`
}

// PasswordResetResponse is returned when a reset is initiated
type PasswordResetResponse struct {
	// Token is the reset token to be delivered out-of-band (e.g. email).
	// The stored procedure may return it for delivery or leave it empty
	// if the delivery is handled entirely in the database.
	Token     string `json:"token"`
	ExpiresIn int64  `json:"expires_in"` // seconds
}

// PasswordResetCompleteRequest completes a password reset using the token
type PasswordResetCompleteRequest struct {
	Token       string `json:"token"`
	NewPassword string `json:"new_password"`
}

// Authenticator handles user authentication operations
type Authenticator interface {
	// Login authenticates credentials and returns a token
	Login(ctx context.Context, req LoginRequest) (*LoginResponse, error)

	// LoginWithCookie authenticates credentials and, when cookie sessions are enabled,
	// writes the session cookie to w. Implementations that do not support cookies
	// should delegate to Login and ignore w.
	LoginWithCookie(ctx context.Context, req LoginRequest, w http.ResponseWriter) (*LoginResponse, error)

	// Logout invalidates a user's session/token
	Logout(ctx context.Context, req LogoutRequest) error

	// LogoutWithCookie invalidates a user's session/token and, when cookie sessions are
	// enabled, clears the session cookie on w. Implementations that do not support cookies
	// should delegate to Logout and ignore w.
	LogoutWithCookie(ctx context.Context, req LogoutRequest, w http.ResponseWriter) error

	// Authenticate extracts and validates user from HTTP request
	// Returns UserContext or error if authentication fails
	Authenticate(r *http.Request) (*UserContext, error)

	// SetAuthenticateCallback registers a fallback called when primary authentication fails.
	// If the callback returns a non-nil UserContext, that result is used instead of the error.
	SetAuthenticateCallback(fn func(r *http.Request) (*UserContext, error))
}

// Registrable allows providers to support user registration
type Registrable interface {
	// Register creates a new user account
	Register(ctx context.Context, req RegisterRequest) (*LoginResponse, error)
}

// ColumnSecurityProvider handles column-level security (masking/hiding)
type ColumnSecurityProvider interface {
	// GetColumnSecurity loads column security rules for a user and entity
	GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)
}

// RowSecurityProvider handles row-level security (filtering)
type RowSecurityProvider interface {
	// GetRowSecurity loads row security rules for a user and entity.
	// userRef identifies the user and is opaque to the caller: it may be an int ID,
	// a string/UUID, or the full *security.UserContext (see SecurityContext.GetUserRef),
	// so providers backed by non-integer user identifiers (e.g. UUIDs) or that need
	// access to JWT claims can implement row security without relying on a numeric ID.
	GetRowSecurity(ctx context.Context, userRef any, schema, table string) (RowSecurity, error)
}

// SecurityProvider is the main interface combining all security concerns
type SecurityProvider interface {
	Authenticator
	ColumnSecurityProvider
	RowSecurityProvider
}

// Optional interfaces for advanced functionality

// Refreshable allows providers to support token refresh
type Refreshable interface {
	// RefreshToken exchanges a refresh token for a new access token
	RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error)
}

// Validatable allows providers to validate tokens without full authentication
type Validatable interface {
	// ValidateToken checks if a token is valid without extracting full user context
	ValidateToken(ctx context.Context, token string) (bool, error)
}

// Cacheable allows providers to support caching of security rules
type Cacheable interface {
	// ClearCache clears cached security rules for a user/entity
	ClearCache(ctx context.Context, userID int, schema, table string) error
}

// PasswordResettable allows providers to support self-service password reset
type PasswordResettable interface {
	// RequestPasswordReset creates a reset token for the given email/username
	RequestPasswordReset(ctx context.Context, req PasswordResetRequest) (*PasswordResetResponse, error)

	// CompletePasswordReset validates the token and sets the new password
	CompletePasswordReset(ctx context.Context, req PasswordResetCompleteRequest) error
}
