package service

import (
	"context"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
	domainAuth "github.com/haydenhargreaves/Potion/internal/domain/auth"
	domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
	domain "github.com/haydenhargreaves/Potion/internal/domain/user"
	"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
	"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
	"golang.org/x/oauth2"
)

// NOTE: HOW THIS WORKS
//
// I need to store the refresh token along side the user in the DB.
// Create a "session token" (cookie) with an expiration and store that in the DB and the session.
// Send this session token along with all the requests (stored in the session) and when
// authorization is needed, use it.
// Once the expiration of MY token expires, prompt the user to log back in.
//
// So what is the point of the Google refresh token? Well, its not really useful right now, but if
// we need to perform Google actions, we can use it to get more access tokens, which are needed for
// the Google actions. Currently, I have no need for it, but it will be stored anyway.
//
// ------------------------------------------------------------------------------------------------

// AuthService implements the domain.AuthService defined in the domain module.
type AuthService struct {
	userRepository domain.UserRepository
	jwtSecret      []byte
	logs           []logging.Logger
}

// Compile-time check to ensure the AuthService implements domain.AuthService
var _ domainAuth.AuthService = (*AuthService)(nil)

// NewAuthService creates a user service object which can be passed into the context. The service
// requires a user repository which it will use to hit the database when needed.
func NewAuthService(userRepository domain.UserRepository, jwtSecret []byte, logs []logging.Logger) domainAuth.AuthService {
	return &AuthService{
		userRepository: userRepository,
		jwtSecret:      jwtSecret,
		logs:           logs,
	}
}

// GetGoogleAuthUrl generates a URL which is used to redirect the user to the Google sign in page.
// This URL is in the Google domain and is not coupled with this application. The data from this URL
// will be passed into a callback and work to complete the Google OAuth workflow.
func (s *AuthService) GetGoogleAuthUrl() string {
	url := auth.GoogleAuthConfig.AuthCodeURL(
		"randomstate",
		oauth2.AccessTypeOffline,
		oauth2.ApprovalForce,
	)

	return url
}

// GoogleAuthSuccess accepts the data from the Google login endpoint and uses it to fetch the users
// data. The data is then used to log the user in or create an account.
func (s *AuthService) GoogleAuthSuccess(state, code string) (string, error) {
	// Ensure the state matches, prevents M.I.T.M. attacks
	if state != "randomstate" {
		return "", fmt.Errorf("States don't match, received %s", state)
	}

	// Get access token from Google
	token, err := auth.GoogleAuthConfig.Exchange(context.Background(), code)
	if err != nil {
		return "", fmt.Errorf("Code exchange failed: %s", err.Error())
	}

	// Use the access token to get user data
	googleUserInfo, err := auth.GetUserData(token.AccessToken)
	if err != nil {
		return "", err
	}

	// Attempt to get the user, user is nil when they don't exit
	user, err := s.userRepository.GetGoogleUser(googleUserInfo.Id)
	if err != nil {
		return "", fmt.Errorf("Failed to get db user: %s", err)
	}

	// A user was found
	if user != nil {
		jwt, err := generateJwt(user.Id, user.Email, s.jwtSecret)
		return jwt, err
	}

	// user did not exist, need to create one
	newUser, err := s.userRepository.CreateGoogleUser(&googleUserInfo, token.RefreshToken)
	if err != nil {
		return "", fmt.Errorf("Repository failed to create user: %s", err.Error())
	}

	jwt, err := generateJwt(newUser.Id, newUser.Email, s.jwtSecret)
	return jwt, err
}

// generateJwt requires user data and returns a JSON web token which can be stored in the browsers
// cookies. This token is used to log a user into the application and allow access to protected
// routes.
func generateJwt(userId int, email string, jwtSecret []byte) (string, error) {
	expiration := time.Now().Add(7 * 24 * time.Hour)

	claims := &domainServer.JwtClaims{
		UserId: userId,
		Email:  email,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expiration),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Subject:   fmt.Sprintf("%d", userId),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(jwtSecret)
	if err != nil {
		return "", fmt.Errorf("Failed to sign jwt: %s", err.Error())
	}

	return tokenString, nil
}

// import (
// 	"bytes"
// 	"time"
//
// 	"github.com/gin-gonic/gin"
// 	"github.com/google/uuid"
// 	"golang.org/x/exp/slog" // or your logging.Logger
// )
//
// // LoggerMiddleware logs HTTP requests with structured fields
// func LoggerMiddleware(logger *slog.Logger) gin.HandlerFunc {
// 	return func(c *gin.Context) {
// 		// Generate request ID for tracing
// 		reqID := uuid.New().String()
// 		start := time.Now()
//
// 		// Capture request body (if enabled)
// 		var reqBody []byte
// 		if c.Request.ContentLength > 0 && c.Request.Body != nil {
// 			reqBody, _ = io.ReadAll(c.Request.Body)
// 			c.Request.Body.Close()
// 			c.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
// 		}
//
// 		// Log request start
// 		logger.Info("request started",
// 			slog.String("req_id", reqID),
// 			slog.String("method", c.Request.Method),
// 			slog.String("path", c.Request.URL.Path),
// 			slog.Int("content_length", int(c.Request.ContentLength)),
// 			slog.String("user_agent", c.Request.UserAgent()),
// 		)
//
// 		// Process request
// 		c.Next()
//
// 		// Log request completion
// 		duration := time.Since(start)
// 		logger.Info("request completed",
// 			slog.String("req_id", reqID),
// 			slog.Int("status", c.Writer.Status()),
// 			slog.String("method", c.Request.Method),
// 			slog.String("path", c.Request.URL.Path),
// 			slog.Duration("duration", duration),
// 			slog.Int("size", c.Writer.Size()),
// 			slog.Any("req_body", string(reqBody)), // truncate if too big
// 		)
// 	}
// }
