package authService

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"git.ma-al.com/goc_daniel/b2b/app/config"
	"git.ma-al.com/goc_daniel/b2b/app/model"
	"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
	"git.ma-al.com/goc_daniel/b2b/app/view"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

const googleUserInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo"

// googleOAuthConfig returns the OAuth2 config for Google
func googleOAuthConfig() *oauth2.Config {
	cfg := config.Get().OAuth.Google
	scopes := cfg.Scopes
	if len(scopes) == 0 {
		scopes = []string{
			"https://www.googleapis.com/auth/userinfo.email",
			"https://www.googleapis.com/auth/userinfo.profile",
		}
	}
	return &oauth2.Config{
		ClientID:     cfg.ClientID,
		ClientSecret: cfg.ClientSecret,
		RedirectURL:  cfg.RedirectURL,
		Scopes:       scopes,
		Endpoint:     google.Endpoint,
	}
}

// GenerateOAuthState generates a random state token for CSRF protection
func (s *AuthService) GenerateOAuthState() (string, error) {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

// GetGoogleAuthURL returns the Google OAuth2 authorization URL with a state token
func (s *AuthService) GetGoogleAuthURL(state string) string {
	return googleOAuthConfig().AuthCodeURL(state, oauth2.AccessTypeOnline)
}

// HandleGoogleCallback exchanges the code for a token, fetches user info,
// and either logs in or registers the user, returning an AuthResponse and raw refresh token.
func (s *AuthService) HandleGoogleCallback(code string) (*model.AuthResponse, string, error) {
	oauthCfg := googleOAuthConfig()

	// Exchange authorization code for token
	token, err := oauthCfg.Exchange(context.Background(), code)
	if err != nil {
		return nil, "", fmt.Errorf("failed to exchange code: %w", err)
	}

	// Fetch user info from Google
	userInfo, err := fetchGoogleUserInfo(oauthCfg.Client(context.Background(), token))
	if err != nil {
		return nil, "", fmt.Errorf("failed to fetch user info: %w", err)
	}

	if !userInfo.VerifiedEmail {
		return nil, "", responseErrors.ErrEmailNotVerified
	}

	// Find or create user
	user, err := s.findOrCreateGoogleUser(userInfo)
	if err != nil {
		return nil, "", err
	}

	// Update last login
	now := time.Now()
	user.LastLoginAt = &now
	s.db.Save(user)

	// Generate access token (JWT)
	accessToken, err := s.generateAccessToken(user)
	if err != nil {
		return nil, "", fmt.Errorf("failed to generate access token: %w", err)
	}

	// Generate opaque refresh token and store in DB
	rawRefreshToken, err := s.createRefreshToken(user.ID)
	if err != nil {
		return nil, "", fmt.Errorf("failed to create refresh token: %w", err)
	}

	return &model.AuthResponse{
		AccessToken: accessToken,
		TokenType:   "Bearer",
		ExpiresIn:   s.config.JWTExpiration,
		User:        user.ToSession(),
	}, rawRefreshToken, nil
}

// findOrCreateGoogleUser finds an existing user by Google provider ID or email,
// or creates a new one.
func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.Customer, error) {
	var user model.Customer

	// Try to find by provider + provider_id
	err := s.db.Where("provider = ? AND provider_id = ?", model.ProviderGoogle, info.ID).First(&user).Error
	if err == nil {
		// Update avatar in case it changed
		user.AvatarURL = info.Picture
		s.db.Save(&user)
		return &user, nil
	}

	// Try to find by email (user may have registered locally before)
	err = s.db.Where("email = ?", info.Email).First(&user).Error
	if err == nil {
		// Link Google provider to existing account
		user.Provider = model.ProviderGoogle
		user.ProviderID = info.ID
		user.AvatarURL = info.Picture
		user.IsActive = true
		s.db.Save(&user)

		// If email has not been verified yet, send email to admin.
		if !user.EmailVerified {
			baseURL := config.Get().App.BaseURL
			if err := s.email.SendNewUserAdminNotification(info.Email, info.Name, baseURL); err != nil {
				// Log error but don't fail registration
				_ = err
			}
		}
		user.EmailVerified = true

		return &user, nil
	}

	// Create new user
	newUser := model.Customer{
		Email:         info.Email,
		FirstName:     info.GivenName,
		LastName:      info.FamilyName,
		Provider:      model.ProviderGoogle,
		ProviderID:    info.ID,
		AvatarURL:     info.Picture,
		Role:          model.RoleUser,
		IsActive:      true,
		EmailVerified: true,
		LangID:        2, // default is english
		CountryID:     2, // default is England
	}

	if err := s.db.Create(&newUser).Error; err != nil {
		return nil, fmt.Errorf("failed to create user: %w", err)
	}

	// If everything succeeded, send email to admin.
	if !user.EmailVerified {
		baseURL := config.Get().App.BaseURL
		if err := s.email.SendNewUserAdminNotification(info.Email, info.Name, baseURL); err != nil {
			// Log error but don't fail registration
			_ = err
		}
	}

	return &newUser, nil
}

// fetchGoogleUserInfo fetches user info from Google using the provided HTTP client
func fetchGoogleUserInfo(client *http.Client) (*view.GoogleUserInfo, error) {
	resp, err := client.Get(googleUserInfoURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var userInfo view.GoogleUserInfo
	if err := json.Unmarshal(body, &userInfo); err != nil {
		return nil, err
	}

	return &userInfo, nil
}
