package services

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/treytartt/honeydue-api/internal/config"
)

const (
	googleTokenInfoURL = "https://oauth2.googleapis.com/tokeninfo"
)

var (
	ErrInvalidGoogleToken    = errors.New("invalid Google ID token")
	ErrGoogleTokenExpired    = errors.New("Google ID token has expired")
	ErrInvalidGoogleAudience = errors.New("invalid Google token audience")
)

// GoogleTokenInfo represents the response from Google's token info endpoint
type GoogleTokenInfo struct {
	Sub           string `json:"sub"`            // Unique Google user ID
	Email         string `json:"email"`          // User's email
	EmailVerified string `json:"email_verified"` // "true" or "false"
	Name          string `json:"name"`           // Full name
	GivenName     string `json:"given_name"`     // First name
	FamilyName    string `json:"family_name"`    // Last name
	Picture       string `json:"picture"`        // Profile picture URL
	Aud           string `json:"aud"`            // Audience (client ID)
	Azp           string `json:"azp"`            // Authorized party
	Exp           string `json:"exp"`            // Expiration time
	Iss           string `json:"iss"`            // Issuer
}

// IsEmailVerified returns whether the email is verified
func (t *GoogleTokenInfo) IsEmailVerified() bool {
	return t.EmailVerified == "true"
}

// GoogleAuthService handles Google Sign In token verification
type GoogleAuthService struct {
	cache  *CacheService
	config *config.Config
	client *http.Client
}

// NewGoogleAuthService creates a new Google auth service
func NewGoogleAuthService(cache *CacheService, cfg *config.Config) *GoogleAuthService {
	return &GoogleAuthService{
		cache:  cache,
		config: cfg,
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// VerifyIDToken verifies a Google ID token and returns the token info
func (s *GoogleAuthService) VerifyIDToken(ctx context.Context, idToken string) (*GoogleTokenInfo, error) {
	// Call Google's tokeninfo endpoint to verify the token
	url := fmt.Sprintf("%s?id_token=%s", googleTokenInfoURL, idToken)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := s.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to verify token: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, ErrInvalidGoogleToken
	}

	var tokenInfo GoogleTokenInfo
	if err := json.NewDecoder(resp.Body).Decode(&tokenInfo); err != nil {
		return nil, fmt.Errorf("failed to decode token info: %w", err)
	}

	// Verify the audience matches our client ID(s)
	if !s.verifyAudience(tokenInfo.Aud, tokenInfo.Azp) {
		return nil, ErrInvalidGoogleAudience
	}

	// Verify the token is not expired (tokeninfo endpoint already checks this,
	// but we double-check for security)
	if tokenInfo.Sub == "" {
		return nil, ErrInvalidGoogleToken
	}

	return &tokenInfo, nil
}

// verifyAudience checks if the token audience matches our client ID(s).
// In production (non-debug), an empty clientID causes verification to fail
// rather than silently bypassing the check.
func (s *GoogleAuthService) verifyAudience(aud, azp string) bool {
	clientID := s.config.GoogleAuth.ClientID
	if clientID == "" {
		if s.config.Server.Debug {
			// In debug mode only, skip audience verification for local development
			return true
		}
		// In production, missing client ID means we cannot verify the audience
		return false
	}

	// Check both aud and azp (Android vs iOS may use different values)
	if aud == clientID || azp == clientID {
		return true
	}

	// Also check Android client ID if configured
	androidClientID := s.config.GoogleAuth.AndroidClientID
	if androidClientID != "" && (aud == androidClientID || azp == androidClientID) {
		return true
	}

	// Also check iOS client ID if configured
	iosClientID := s.config.GoogleAuth.IOSClientID
	if iosClientID != "" && (aud == iosClientID || azp == iosClientID) {
		return true
	}

	return false
}
