package services

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

	"mengpost-backend/config"
)

var msTokenURL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
var msAuthURL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize"

// MicrosoftOAuthScopes IMAP + SMTP + 刷新令牌.
var MicrosoftOAuthScopes = []string{
	"openid",
	"email",
	"offline_access",
	"https://outlook.office.com/IMAP.AccessAsUser.All",
	"https://outlook.office.com/SMTP.Send",
}

// MicrosoftAuthCodeURL 生成跳转微软登录的 URL.
func MicrosoftAuthCodeURL(cfg *config.Config, state string) string {
	q := url.Values{}
	q.Set("client_id", cfg.MSClientID)
	q.Set("response_type", "code")
	q.Set("redirect_uri", cfg.MSRedirectURL)
	q.Set("response_mode", "query")
	q.Set("scope", strings.Join(MicrosoftOAuthScopes, " "))
	q.Set("state", state)
	return msAuthURL + "?" + q.Encode()
}

type msTokenResp struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	IDToken      string `json:"id_token"`
	ExpiresIn    int    `json:"expires_in"`
	Error        string `json:"error"`
	Description  string `json:"error_description"`
}

// ExchangeMicrosoftCode 用授权码换令牌并解析邮箱（id_token）.
func ExchangeMicrosoftCode(ctx context.Context, cfg *config.Config, code string) (email, refresh, access string, accessExpiry time.Time, err error) {
	form := url.Values{}
	form.Set("client_id", cfg.MSClientID)
	if cfg.MSClientSecret != "" {
		form.Set("client_secret", cfg.MSClientSecret)
	}
	form.Set("code", code)
	form.Set("redirect_uri", cfg.MSRedirectURL)
	form.Set("grant_type", "authorization_code")

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, msTokenURL, strings.NewReader(form.Encode()))
	if err != nil {
		return "", "", "", time.Time{}, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", "", "", time.Time{}, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var tr msTokenResp
	if err := json.Unmarshal(body, &tr); err != nil {
		return "", "", "", time.Time{}, fmt.Errorf("token 响应解析失败: %w", err)
	}
	if tr.Error != "" {
		return "", "", "", time.Time{}, fmt.Errorf("微软 token错误: %s %s", tr.Error, tr.Description)
	}
	if tr.RefreshToken == "" {
		return "", "", "", time.Time{}, fmt.Errorf("未返回 refresh_token，请确认 scope 含 offline_access")
	}
	email, err = emailFromIDToken(tr.IDToken)
	if err != nil || email == "" {
		return "", "", "", time.Time{}, fmt.Errorf("无法从 id_token 解析邮箱: %w", err)
	}
	exp := time.Now().Add(time.Duration(tr.ExpiresIn) * time.Second)
	return strings.ToLower(strings.TrimSpace(email)), tr.RefreshToken, tr.AccessToken, exp, nil
}

func emailFromIDToken(idt string) (string, error) {
	if idt == "" {
		return "", fmt.Errorf("无 id_token")
	}
	parts := strings.Split(idt, ".")
	if len(parts) < 2 {
		return "", fmt.Errorf("id_token 格式异常")
	}
	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return "", err
	}
	var claims struct {
		Email             string `json:"email"`
		PreferredUsername string `json:"preferred_username"`
		UPN               string `json:"upn"`
	}
	if err := json.Unmarshal(payload, &claims); err != nil {
		return "", err
	}
	if claims.Email != "" {
		return claims.Email, nil
	}
	if claims.PreferredUsername != "" {
		return claims.PreferredUsername, nil
	}
	if claims.UPN != "" {
		return claims.UPN, nil
	}
	return "", fmt.Errorf("claims 中无邮箱字段")
}

// MicrosoftAccessToken 用 refresh_token 换新 access_token（及可能的新 refresh）.
type MicrosoftAccessToken struct {
	AccessToken  string
	RefreshToken string // 若微软返回新的则替换存库
	Expiry       time.Time
}

func RefreshMicrosoftAccess(ctx context.Context, cfg *config.Config, refresh string) (*MicrosoftAccessToken, error) {
	form := url.Values{}
	form.Set("client_id", cfg.MSClientID)
	if cfg.MSClientSecret != "" {
		form.Set("client_secret", cfg.MSClientSecret)
	}
	form.Set("refresh_token", refresh)
	form.Set("grant_type", "refresh_token")
	form.Set("scope", strings.Join(MicrosoftOAuthScopes, " "))

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, msTokenURL, strings.NewReader(form.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var tr msTokenResp
	if err := json.Unmarshal(body, &tr); err != nil {
		return nil, err
	}
	if tr.Error != "" {
		return nil, fmt.Errorf("%s: %s", tr.Error, tr.Description)
	}
	out := &MicrosoftAccessToken{
		AccessToken: tr.AccessToken,
		Expiry:      time.Now().Add(time.Duration(tr.ExpiresIn) * time.Second),
	}
	if tr.RefreshToken != "" {
		out.RefreshToken = tr.RefreshToken
	}
	return out, nil
}
