package zitadel

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
)

// IDTokenClaims holds selected OIDC id_token claims used by registration/login flows.
type IDTokenClaims struct {
	Sub           string
	Email         string
	EmailVerified bool
	Name          string
	Locale        string
}

// AuthorizeURL builds the ZITADEL OIDC authorization URL for social registration/login.
func (c *Client) AuthorizeURL(redirectURI, state, provider string) (string, error) {
	if c == nil {
		return "", ErrNotConfigured
	}
	if c.conf.OAuthClientID == "" {
		return "", fmt.Errorf("zitadel: oauth client id is required for authorize url")
	}
	if redirectURI == "" || state == "" {
		return "", fmt.Errorf("zitadel: redirect_uri and state are required")
	}
	q := url.Values{}
	q.Set("client_id", c.conf.OAuthClientID)
	q.Set("redirect_uri", redirectURI)
	q.Set("response_type", "code")
	q.Set("scope", "openid profile email")
	q.Set("state", state)
	switch strings.ToLower(strings.TrimSpace(provider)) {
	case "google":
		if c.conf.GoogleIdPID != "" {
			q.Set("idp_id", c.conf.GoogleIdPID)
		}
	case "ldap":
		if c.conf.LdapIdPID != "" {
			q.Set("idp_id", c.conf.LdapIdPID)
		}
	}
	return c.issuer + "/oauth/v2/authorize?" + q.Encode(), nil
}

// ExchangeAuthorizationCode exchanges an OAuth authorization code for tokens.
func (c *Client) ExchangeAuthorizationCode(ctx context.Context, code, redirectURI string) (*TokenResult, error) {
	if c == nil {
		return nil, ErrNotConfigured
	}
	if code == "" || redirectURI == "" {
		return nil, fmt.Errorf("zitadel: code and redirect_uri are required")
	}
	if c.conf.OAuthClientID == "" || c.conf.OAuthClientSecret == "" {
		return nil, fmt.Errorf("zitadel: oauth client credentials are required for code exchange")
	}
	form := url.Values{}
	form.Set("grant_type", "authorization_code")
	form.Set("client_id", c.conf.OAuthClientID)
	form.Set("client_secret", c.conf.OAuthClientSecret)
	form.Set("code", code)
	form.Set("redirect_uri", redirectURI)
	return c.postToken(ctx, form)
}

// FetchUserInfo loads OIDC userinfo using an access token.
func (c *Client) FetchUserInfo(ctx context.Context, accessToken string) (*IDTokenClaims, error) {
	if c == nil {
		return nil, ErrNotConfigured
	}
	if accessToken == "" {
		return nil, fmt.Errorf("zitadel: access token is required")
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.issuer+"/oidc/v1/userinfo", http.NoBody)
	if err != nil {
		return nil, fmt.Errorf("zitadel: userinfo request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+accessToken)
	req.Header.Set("Accept", "application/json")

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

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("zitadel: read userinfo response: %w", err)
	}
	if resp.StatusCode == http.StatusUnauthorized {
		return nil, ErrInvalidCredentials
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("zitadel: userinfo request: status %d: %s", resp.StatusCode, truncateBody(raw))
	}
	var info struct {
		Sub           string `json:"sub"`
		Email         string `json:"email"`
		EmailVerified bool   `json:"email_verified"`
		Name          string `json:"name"`
		Locale        string `json:"locale"`
	}
	if err := json.Unmarshal(raw, &info); err != nil {
		return nil, fmt.Errorf("zitadel: decode userinfo response: %w", err)
	}
	if info.Sub == "" {
		return nil, errors.New("zitadel: userinfo missing sub")
	}
	return &IDTokenClaims{
		Sub:           info.Sub,
		Email:         info.Email,
		EmailVerified: info.EmailVerified,
		Name:          info.Name,
		Locale:        info.Locale,
	}, nil
}

func ParseIDTokenClaims(idToken string) (*IDTokenClaims, error) {
	if idToken == "" {
		return nil, errors.New("zitadel: id_token is empty")
	}
	parts := strings.Split(idToken, ".")
	if len(parts) < 2 {
		return nil, errors.New("zitadel: malformed id_token")
	}
	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, fmt.Errorf("zitadel: decode id_token payload: %w", err)
	}
	var raw struct {
		Sub           string `json:"sub"`
		Email         string `json:"email"`
		EmailVerified bool   `json:"email_verified"`
		Name          string `json:"name"`
		Locale        string `json:"locale"`
	}
	if err := json.Unmarshal(payload, &raw); err != nil {
		return nil, fmt.Errorf("zitadel: unmarshal id_token payload: %w", err)
	}
	if raw.Sub == "" {
		return nil, errors.New("zitadel: id_token missing sub")
	}
	return &IDTokenClaims{
		Sub:           raw.Sub,
		Email:         raw.Email,
		EmailVerified: raw.EmailVerified,
		Name:          raw.Name,
		Locale:        raw.Locale,
	}, nil
}
