package oauth

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

	"golang.org/x/oauth2"

	"security-center-backend/internal/config"
)

type Auth0Client struct {
	cfg        config.OAuthConfig
	impl       *oauth2.Config
	httpClient *http.Client
	domain     string
	audience   string
}

func NewAuth0Client(cfg config.OAuthConfig) *Auth0Client {
	if !cfg.Enabled || cfg.Auth0Domain == "" || cfg.Auth0ClientID == "" || cfg.Auth0ClientSecret == "" {
		return &Auth0Client{cfg: cfg}
	}

	authEndpoint := oauth2.Endpoint{
		AuthURL:  fmt.Sprintf("https://%s/authorize", cfg.Auth0Domain),
		TokenURL: fmt.Sprintf("https://%s/oauth/token", cfg.Auth0Domain),
	}

	scopes := cfg.Scopes
	if len(scopes) == 0 {
		scopes = []string{"openid", "profile", "email"}
	}

	return &Auth0Client{
		cfg: cfg,
		impl: &oauth2.Config{
			ClientID:     cfg.Auth0ClientID,
			ClientSecret: cfg.Auth0ClientSecret,
			RedirectURL:  cfg.RedirectURL,
			Scopes:       scopes,
			Endpoint:     authEndpoint,
		},
		httpClient: &http.Client{Timeout: 30 * time.Second},
		domain:     cfg.Auth0Domain,
		audience:   cfg.Auth0Audience,
	}
}

func (c *Auth0Client) AuthCodeURL(state string) (string, error) {
	if c.impl == nil {
		return "", ErrDisabled
	}
	opts := []oauth2.AuthCodeOption{oauth2.AccessTypeOffline}
	if c.audience != "" {
		opts = append(opts, oauth2.SetAuthURLParam("audience", c.audience))
	}
	return c.impl.AuthCodeURL(state, opts...), nil
}

func (c *Auth0Client) Exchange(ctx context.Context, code string) (*oauth2.Token, error) {
	if c.impl == nil {
		return nil, ErrDisabled
	}
	return c.impl.Exchange(ctx, code)
}

func (c *Auth0Client) UserInfo(ctx context.Context, token *oauth2.Token) (*Auth0UserInfo, error) {
	if c.impl == nil {
		return nil, ErrDisabled
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/userinfo", c.domain), nil)
	if err != nil {
		return nil, fmt.Errorf("create userinfo request: %w", err)
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("auth0 userinfo request: %w", err)
	}
	defer resp.Body.Close()

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

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

type Auth0UserInfo struct {
	Sub           string `json:"sub"`
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`
	Name          string `json:"name"`
	Picture       string `json:"picture"`
	GivenName     string `json:"given_name"`
	FamilyName    string `json:"family_name"`
	UpdatedAt     string `json:"updated_at"`
}
