package handlers

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"errors"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"mengpost-backend/config"
	"mengpost-backend/models"
	"mengpost-backend/services"
)

const (
	msSMTPHost   = "smtp.office365.com"
	msSMTPPort   = 587
	msIMAPHost   = "outlook.office365.com"
	msIMAPPort   = 993
	oauthPath    = "/admin/accounts"
	tokenTimeout = 90 * time.Second
)

func randomOAuthState() (string, error) {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

// MicrosoftOAuthEnabled GET /api/oauth/microsoft/enabled
func MicrosoftOAuthEnabled(cfg *config.Config) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"enabled": cfg.MicrosoftOAuthEnabled()})
	}
}

// MicrosoftOAuthStart GET /api/oauth/microsoft/start
func MicrosoftOAuthStart(cfg *config.Config) gin.HandlerFunc {
	return func(c *gin.Context) {
		if !cfg.MicrosoftOAuthEnabled() {
			c.JSON(http.StatusServiceUnavailable, gin.H{"error": "未配置微软 OAuth（MP_MS_CLIENT_ID）"})
			return
		}
		state, err := randomOAuthState()
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		if err := models.InsertOAuthPendingState(state); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		authURL := services.MicrosoftAuthCodeURL(cfg, state)
		c.JSON(http.StatusOK, gin.H{"url": authURL, "state": state})
	}
}

// MicrosoftOAuthCallback GET /api/oauth/microsoft/callback（微软公网回调，无 token）
func MicrosoftOAuthCallback(cfg *config.Config) gin.HandlerFunc {
	frontendAccounts := strings.TrimSuffix(cfg.FrontendURL, "/") + oauthPath
	return func(c *gin.Context) {
		q := c.Request.URL.Query()
		if errMsg := q.Get("error"); errMsg != "" {
			desc := q.Get("error_description")
			msg := errMsg
			if desc != "" {
				msg = errMsg + ": " + desc
			}
			c.Redirect(http.StatusFound, frontendAccounts+"?oauth_err="+url.QueryEscape(msg))
			return
		}
		state := q.Get("state")
		code := q.Get("code")
		if state == "" || code == "" {
			c.Redirect(http.StatusFound, frontendAccounts+"?oauth_err="+url.QueryEscape("缺少 code 或 state"))
			return
		}
		ctx, cancel := context.WithTimeout(c.Request.Context(), tokenTimeout)
		defer cancel()
		email, refresh, _, _, err := services.ExchangeMicrosoftCode(ctx, cfg, code)
		if err != nil {
			c.Redirect(http.StatusFound, frontendAccounts+"?oauth_err="+url.QueryEscape(err.Error()))
			return
		}
		if err := models.UpdateOAuthPendingTokens(state, refresh, email); err != nil {
			c.Redirect(http.StatusFound, frontendAccounts+"?oauth_err="+url.QueryEscape(err.Error()))
			return
		}
		c.Redirect(http.StatusFound, frontendAccounts+"?oauth_ms="+url.QueryEscape(state))
	}
}

type microsoftFinishBody struct {
	State string `json:"state"`
}

// MicrosoftOAuthFinish POST /api/oauth/microsoft/finish
func MicrosoftOAuthFinish() gin.HandlerFunc {
	return func(c *gin.Context) {
		var body microsoftFinishBody
		if err := c.ShouldBindJSON(&body); err != nil || body.State == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "需要 JSON: {\"state\":\"...\"}"})
			return
		}
		refresh, email, err := models.TakeOAuthPending(body.State)
		if err != nil {
			if errors.Is(err, models.ErrOAuthPendingNotFound) {
				c.JSON(http.StatusBadRequest, gin.H{"error": "登录会话无效或已使用，请重新发起 OAuth"})
				return
			}
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		email = strings.ToLower(strings.TrimSpace(email))

		acc, err := models.GetAccountByEmail(email)
		if err != nil && !errors.Is(err, sql.ErrNoRows) {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		if err == nil {
			acc.AuthType = models.AuthTypeOAuthMicrosoft
			acc.OAuthRefreshToken = refresh
			acc.SMTPHost = msSMTPHost
			acc.SMTPPort = msSMTPPort
			acc.IMAPHost = msIMAPHost
			acc.IMAPPort = msIMAPPort
			acc.UseTLS = true
			if err := models.UpdateAccount(acc); err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			acc.Password = ""
			acc.OAuthRefreshToken = ""
			c.JSON(http.StatusOK, acc)
			return
		}

		local := email
		if at := strings.IndexByte(email, '@'); at > 0 {
			local = email[:at]
		}
		na := &models.Account{
			Name:              local,
			Email:             email,
			Password:          "",
			SMTPHost:          msSMTPHost,
			SMTPPort:          msSMTPPort,
			IMAPHost:          msIMAPHost,
			IMAPPort:          msIMAPPort,
			UseTLS:            true,
			AuthType:          models.AuthTypeOAuthMicrosoft,
			OAuthRefreshToken: refresh,
		}
		if err := models.CreateAccount(na); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		na.Password = ""
		na.OAuthRefreshToken = ""
		c.JSON(http.StatusOK, na)
	}
}
