package auth

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

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/github"
	"golang.org/x/oauth2/google"
)

// OAuthProvider represents an OAuth2 provider
type OAuthProvider string

const (
	ProviderGitHub OAuthProvider = "github"
	ProviderGoogle OAuthProvider = "google"
)

// OAuthUser contains user information from OAuth provider
type OAuthUser struct {
	Provider OAuthProvider
	ID       string
	Email    string
	Name     string
	Avatar   string
}

// OAuthManager handles OAuth2 authentication
type OAuthManager struct {
	githubConfig *oauth2.Config
	googleConfig *oauth2.Config
}

// NewOAuthManager creates a new OAuth manager
func NewOAuthManager(baseURL, githubClientID, githubClientSecret, googleClientID, googleClientSecret string) *OAuthManager {
	m := &OAuthManager{}

	if githubClientID != "" && githubClientSecret != "" {
		m.githubConfig = &oauth2.Config{
			ClientID:     githubClientID,
			ClientSecret: githubClientSecret,
			Endpoint:     github.Endpoint,
			Scopes:       []string{"read:user", "user:email"},
			RedirectURL:  baseURL + "/v1/auth/oauth/github/callback",
		}
	}

	if googleClientID != "" && googleClientSecret != "" {
		m.googleConfig = &oauth2.Config{
			ClientID:     googleClientID,
			ClientSecret: googleClientSecret,
			Endpoint:     google.Endpoint,
			Scopes:       []string{"openid", "email", "profile"},
			RedirectURL:  baseURL + "/v1/auth/oauth/google/callback",
		}
	}

	return m
}

// GetAuthURL returns the OAuth authorization URL for the given provider
func (m *OAuthManager) GetAuthURL(provider OAuthProvider, state string) (string, error) {
	config, err := m.getConfig(provider)
	if err != nil {
		return "", err
	}
	return config.AuthCodeURL(state, oauth2.AccessTypeOffline), nil
}

// Exchange exchanges an authorization code for user information
func (m *OAuthManager) Exchange(ctx context.Context, provider OAuthProvider, code string) (*OAuthUser, error) {
	config, err := m.getConfig(provider)
	if err != nil {
		return nil, err
	}

	token, err := config.Exchange(ctx, code)
	if err != nil {
		return nil, fmt.Errorf("exchange code: %w", err)
	}

	return m.fetchUserInfo(ctx, provider, token)
}

func (m *OAuthManager) getConfig(provider OAuthProvider) (*oauth2.Config, error) {
	switch provider {
	case ProviderGitHub:
		if m.githubConfig == nil {
			return nil, fmt.Errorf("github oauth not configured")
		}
		return m.githubConfig, nil
	case ProviderGoogle:
		if m.googleConfig == nil {
			return nil, fmt.Errorf("google oauth not configured")
		}
		return m.googleConfig, nil
	default:
		return nil, fmt.Errorf("unknown provider: %s", provider)
	}
}

func (m *OAuthManager) fetchUserInfo(ctx context.Context, provider OAuthProvider, token *oauth2.Token) (*OAuthUser, error) {
	switch provider {
	case ProviderGitHub:
		return m.fetchGitHubUser(ctx, token)
	case ProviderGoogle:
		return m.fetchGoogleUser(ctx, token)
	default:
		return nil, fmt.Errorf("unknown provider: %s", provider)
	}
}

func (m *OAuthManager) fetchGitHubUser(ctx context.Context, token *oauth2.Token) (*OAuthUser, error) {
	client := m.githubConfig.Client(ctx, token)

	// Fetch user info
	resp, err := client.Get("https://api.github.com/user")
	if err != nil {
		return nil, fmt.Errorf("fetch user: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("github api returned %d", resp.StatusCode)
	}

	var ghUser struct {
		ID        int64  `json:"id"`
		Login     string `json:"login"`
		Name      string `json:"name"`
		Email     string `json:"email"`
		AvatarURL string `json:"avatar_url"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&ghUser); err != nil {
		return nil, fmt.Errorf("decode user: %w", err)
	}

	// If email not public, fetch from emails endpoint
	email := ghUser.Email
	if email == "" {
		email, _ = m.fetchGitHubEmail(ctx, client)
	}

	name := ghUser.Name
	if name == "" {
		name = ghUser.Login
	}

	return &OAuthUser{
		Provider: ProviderGitHub,
		ID:       fmt.Sprintf("%d", ghUser.ID),
		Email:    email,
		Name:     name,
		Avatar:   ghUser.AvatarURL,
	}, nil
}

func (m *OAuthManager) fetchGitHubEmail(ctx context.Context, client *http.Client) (string, error) {
	resp, err := client.Get("https://api.github.com/user/emails")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var emails []struct {
		Email    string `json:"email"`
		Primary  bool   `json:"primary"`
		Verified bool   `json:"verified"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&emails); err != nil {
		return "", err
	}

	// Find primary verified email
	for _, e := range emails {
		if e.Primary && e.Verified {
			return e.Email, nil
		}
	}

	// Fall back to any verified email
	for _, e := range emails {
		if e.Verified {
			return e.Email, nil
		}
	}

	return "", nil
}

func (m *OAuthManager) fetchGoogleUser(ctx context.Context, token *oauth2.Token) (*OAuthUser, error) {
	client := m.googleConfig.Client(ctx, token)

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

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("google api returned %d", resp.StatusCode)
	}

	var gUser struct {
		ID            string `json:"id"`
		Email         string `json:"email"`
		VerifiedEmail bool   `json:"verified_email"`
		Name          string `json:"name"`
		Picture       string `json:"picture"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&gUser); err != nil {
		return nil, fmt.Errorf("decode user: %w", err)
	}

	return &OAuthUser{
		Provider: ProviderGoogle,
		ID:       gUser.ID,
		Email:    gUser.Email,
		Name:     gUser.Name,
		Avatar:   gUser.Picture,
	}, nil
}

// IsGitHubConfigured returns true if GitHub OAuth is configured
func (m *OAuthManager) IsGitHubConfigured() bool {
	return m.githubConfig != nil
}

// IsGoogleConfigured returns true if Google OAuth is configured
func (m *OAuthManager) IsGoogleConfigured() bool {
	return m.googleConfig != nil
}
