package gateway

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/service"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/jwt"
)

// RefreshTokenService handles token refresh operations
type RefreshTokenService interface {
	RefreshToken(ctx context.Context, input *RefreshTokenInput) (*RefreshTokenResponse, error)
}

// RefreshTokenInput represents the input for token refresh
type RefreshTokenInput struct {
	RefreshToken string
}

// RefreshTokenResponse represents the response after successful token refresh
type RefreshTokenResponse struct {
	// User details
	UserID    string `json:"user_id"`
	UserEmail string `json:"user_email"`
	UserName  string `json:"user_name"`
	UserRole  string `json:"user_role"`

	// Tenant details
	TenantID string `json:"tenant_id"`

	// Session and new tokens
	SessionID     string    `json:"session_id"`
	AccessToken   string    `json:"access_token"`
	AccessExpiry  time.Time `json:"access_expiry"`
	RefreshToken  string    `json:"refresh_token"`
	RefreshExpiry time.Time `json:"refresh_expiry"`

	RefreshedAt time.Time `json:"refreshed_at"`
}

type refreshTokenService struct {
	sessionService service.SessionService
	jwtProvider    jwt.Provider
	logger         *zap.Logger
}

// NewRefreshTokenService creates a new refresh token service
func NewRefreshTokenService(
	sessionService service.SessionService,
	jwtProvider jwt.Provider,
	logger *zap.Logger,
) RefreshTokenService {
	return &refreshTokenService{
		sessionService: sessionService,
		jwtProvider:    jwtProvider,
		logger:         logger.Named("refresh-token-service"),
	}
}

// RefreshToken validates the refresh token and generates new access/refresh tokens
// CWE-613: Validates session still exists before issuing new tokens
func (s *refreshTokenService) RefreshToken(ctx context.Context, input *RefreshTokenInput) (*RefreshTokenResponse, error) {
	s.logger.Info("processing token refresh request")

	// Validate the refresh token and extract session ID
	sessionID, err := s.jwtProvider.ValidateToken(input.RefreshToken)
	if err != nil {
		s.logger.Warn("invalid refresh token", zap.Error(err))
		return nil, fmt.Errorf("invalid or expired refresh token")
	}

	s.logger.Debug("refresh token validated", zap.String("session_id", sessionID))

	// Retrieve the session to ensure it still exists
	// CWE-613: This prevents using a refresh token after logout/session deletion
	session, err := s.sessionService.GetSession(ctx, sessionID)
	if err != nil {
		s.logger.Warn("session not found or expired",
			zap.String("session_id", sessionID),
			zap.Error(err))
		return nil, fmt.Errorf("session not found or expired")
	}

	s.logger.Info("session retrieved for token refresh",
		zap.String("session_id", sessionID),
		zap.String("user_id", session.UserUUID.String()),
		zap.String("tenant_id", session.TenantID.String()))

	// Generate new JWT access and refresh tokens
	// Both tokens are regenerated to maintain rotation best practices
	accessToken, accessExpiry, refreshToken, refreshExpiry, err := s.jwtProvider.GenerateTokenPair(
		session.ID,
		AccessTokenDuration,
		RefreshTokenDuration,
	)
	if err != nil {
		s.logger.Error("failed to generate new token pair", zap.Error(err))
		return nil, fmt.Errorf("failed to generate new tokens")
	}

	s.logger.Info("token refresh completed successfully",
		zap.String("user_id", session.UserUUID.String()),
		zap.String("tenant_id", session.TenantID.String()),
		zap.String("session_id", session.ID))

	return &RefreshTokenResponse{
		UserID:        session.UserUUID.String(),
		UserEmail:     session.UserEmail,
		UserName:      session.UserName,
		UserRole:      session.UserRole,
		TenantID:      session.TenantID.String(),
		SessionID:     session.ID,
		AccessToken:   accessToken,
		AccessExpiry:  accessExpiry,
		RefreshToken:  refreshToken,
		RefreshExpiry: refreshExpiry,
		RefreshedAt:   time.Now().UTC(),
	}, nil
}
