package authentication

import (
	"Yimaru-Backend/internal/domain"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"

	"cloud.google.com/go/auth/credentials/idtoken"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

var googleOAuthConfig *oauth2.Config

var ErrGoogleOAuthNotInitialized = errors.New("google oauth not initialized")

func (s *Service) InitGoogleOAuth(clientID, clientSecret, redirectURL string) {
	if clientID == "" || clientSecret == "" || redirectURL == "" {
		return
	}
	googleOAuthConfig = &oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		RedirectURL:  redirectURL,
		Scopes: []string{
			"https://www.googleapis.com/auth/userinfo.email",
			"https://www.googleapis.com/auth/userinfo.profile",
		},
		Endpoint: google.Endpoint,
	}
}

func (s *Service) GenerateGoogleLoginURL(state string) string {
	if googleOAuthConfig == nil {
		return ""
	}
	return googleOAuthConfig.AuthCodeURL(state, oauth2.AccessTypeOffline)
}

func (s *Service) ExchangeGoogleCode(
	ctx context.Context,
	code string,
) (*oauth2.Token, error) {
	if googleOAuthConfig == nil {
		return nil, ErrGoogleOAuthNotInitialized
	}
	if code == "" {
		return nil, errors.New("missing google auth code")
	}
	return googleOAuthConfig.Exchange(ctx, code)
}

func (s *Service) FetchGoogleUser(
	ctx context.Context,
	token *oauth2.Token,
) (*domain.GoogleUser, error) {
	if googleOAuthConfig == nil {
		return nil, ErrGoogleOAuthNotInitialized
	}
	if token == nil {
		return nil, errors.New("missing google oauth token")
	}

	client := googleOAuthConfig.Client(ctx, token)
	resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
	if err != nil {
		return nil, fmt.Errorf("failed to fetch google user: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("google userinfo failed: status=%d body=%s", resp.StatusCode, string(body))
	}

	var user domain.GoogleUser
	if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
		return nil, fmt.Errorf("failed to decode google user: %w", err)
	}

	if user.Email == "" {
		return nil, errors.New("google account has no email")
	}

	return &user, nil
}

func (s *Service) LoginWithGoogleAndroid(
	ctx context.Context,
	idToken string,
	clientID string,
) (domain.LoginSuccess, error) {

	if idToken == "" {
		return domain.LoginSuccess{}, errors.New("missing id token")
	}

	if clientID == "" {
		return domain.LoginSuccess{}, errors.New("missing google client id")
	}

	payload, err := idtoken.Validate(ctx, idToken, clientID)
	if err != nil {
		return domain.LoginSuccess{}, errors.New("invalid google id token")
	}

	email, _ := payload.Claims["email"].(string)
	if email == "" {
		return domain.LoginSuccess{}, errors.New("google id token missing email")
	}

	verified := false
	switch v := payload.Claims["email_verified"].(type) {
	case bool:
		verified = v
	case string:
		verified = (v == "true")
	}

	givenName, _ := payload.Claims["given_name"].(string)
	familyName, _ := payload.Claims["family_name"].(string)
	picture, _ := payload.Claims["picture"].(string)

	gUser := domain.GoogleUser{
		ID:            payload.Subject,
		Email:         email,
		VerifiedEmail: verified,
		GivenName:     givenName,
		FamilyName:    familyName,
		Picture:       picture,
	}

	return s.LoginWithGoogle(ctx, gUser)
}

func (s *Service) LoginWithGoogle(
	ctx context.Context,
	gUser domain.GoogleUser,
) (domain.LoginSuccess, error) {

	var user domain.User
	var err error

	user, err = s.userStore.GetUserByGoogleID(ctx, gUser.ID)
	if err != nil {
		if !errors.Is(err, domain.ErrUserNotFound) {
			return domain.LoginSuccess{}, err
		}

		user, err = s.userStore.GetUserByEmailPhone(ctx, gUser.Email, "")
		if err != nil {
			if !errors.Is(err, domain.ErrUserNotFound) {
				return domain.LoginSuccess{}, err
			}

			user, err = s.userStore.CreateGoogleUser(ctx, gUser)
			if err != nil {
				return domain.LoginSuccess{}, err
			}
		} else {
			if err := s.userStore.LinkGoogleAccount(ctx, user.ID, gUser.ID); err != nil {
				return domain.LoginSuccess{}, err
			}
		}
	}

	if user.Status == domain.UserStatusPending {
		return domain.LoginSuccess{}, domain.ErrUserNotVerified
	}

	if user.Status == domain.UserStatusSuspended {
		return domain.LoginSuccess{}, ErrUserSuspended
	}

	oldToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)
	if err != nil {
		if !errors.Is(err, ErrRefreshTokenNotFound) {
			return domain.LoginSuccess{}, err
		}
	} else if !oldToken.Revoked {
		if err := s.tokenStore.RevokeRefreshToken(ctx, oldToken.Token); err != nil {
			return domain.LoginSuccess{}, err
		}
	}

	refreshToken, err := generateRefreshToken()
	if err != nil {
		return domain.LoginSuccess{}, err
	}

	if err := s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
		Token:     refreshToken,
		UserID:    user.ID,
		CreatedAt: time.Now(),
		ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
	}); err != nil {
		return domain.LoginSuccess{}, err
	}

	return domain.LoginSuccess{
		UserId:  user.ID,
		Role:    user.Role,
		RfToken: refreshToken,
	}, nil
}

func (s *Service) verifyGoogleIDToken(
	ctx context.Context,
	idToken string,
	clientID string,
) (*idtoken.Payload, error) {

	payload, err := idtoken.Validate(ctx, idToken, clientID)
	if err != nil {
		return nil, err
	}

	return payload, nil
}
