package storage

import (
	"errors"
	"net/url"
	"strings"

	"gorm.io/gorm"

	"sproutgate-backend/internal/models"
)

const defaultGiteaBaseURL = "https://git.shumengya.top"
const defaultLinuxdoConnectBaseURL = "https://connect.linux.do"
const defaultMicrosoftTenant = "common"

func (s *Store) loadOrCreateOAuthConfig() error {
	var cfg OAuthConfig
	found, err := s.getConfig("oauth", &cfg)
	if err != nil {
		return err
	}
	changed := !found
	if strings.TrimSpace(cfg.GiteaBaseURL) == "" {
		cfg.GiteaBaseURL = defaultGiteaBaseURL
		changed = true
	}
	cfg.GiteaBaseURL = strings.TrimRight(strings.TrimSpace(cfg.GiteaBaseURL), "/")
	if strings.TrimSpace(cfg.LinuxdoConnectBaseURL) == "" {
		cfg.LinuxdoConnectBaseURL = defaultLinuxdoConnectBaseURL
		changed = true
	}
	cfg.LinuxdoConnectBaseURL = strings.TrimRight(strings.TrimSpace(cfg.LinuxdoConnectBaseURL), "/")
	if strings.TrimSpace(cfg.MicrosoftTenant) == "" {
		cfg.MicrosoftTenant = defaultMicrosoftTenant
		changed = true
	} else {
		cfg.MicrosoftTenant = strings.TrimSpace(cfg.MicrosoftTenant)
	}
	if len(cfg.AllowedReturnPrefixes) == 0 {
		cfg.AllowedReturnPrefixes = []string{
			"http://localhost:5173/",
			"http://127.0.0.1:5173/",
		}
		changed = true
	}
	if changed {
		if err := s.setConfig("oauth", cfg); err != nil {
			return err
		}
	}
	s.mu.Lock()
	s.oauthConfig = cfg
	s.mu.Unlock()
	return nil
}

// GetOAuthConfig 返回当前 OAuth 设置副本（含密钥；仅服务端使用）。
func (s *Store) GetOAuthConfig() OAuthConfig {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.oauthConfig
}

// MergeUpdateOAuthConfig 更新 OAuth 设置；空字符串的 ClientSecret 表示保留原值。
func (s *Store) MergeUpdateOAuthConfig(in OAuthConfig) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	prev := s.oauthConfig
	if strings.TrimSpace(in.GitHubClientSecret) == "" {
		in.GitHubClientSecret = prev.GitHubClientSecret
	}
	if strings.TrimSpace(in.GiteaClientSecret) == "" {
		in.GiteaClientSecret = prev.GiteaClientSecret
	}
	if strings.TrimSpace(in.LinuxdoClientSecret) == "" {
		in.LinuxdoClientSecret = prev.LinuxdoClientSecret
	}
	if strings.TrimSpace(in.GoogleClientSecret) == "" {
		in.GoogleClientSecret = prev.GoogleClientSecret
	}
	if strings.TrimSpace(in.MicrosoftClientSecret) == "" {
		in.MicrosoftClientSecret = prev.MicrosoftClientSecret
	}
	if strings.TrimSpace(in.GiteaBaseURL) == "" {
		in.GiteaBaseURL = defaultGiteaBaseURL
	}
	in.GiteaBaseURL = strings.TrimRight(strings.TrimSpace(in.GiteaBaseURL), "/")
	if strings.TrimSpace(in.LinuxdoConnectBaseURL) == "" {
		in.LinuxdoConnectBaseURL = defaultLinuxdoConnectBaseURL
	}
	in.LinuxdoConnectBaseURL = strings.TrimRight(strings.TrimSpace(in.LinuxdoConnectBaseURL), "/")
	if in.AllowedReturnPrefixes == nil || len(in.AllowedReturnPrefixes) == 0 {
		in.AllowedReturnPrefixes = prev.AllowedReturnPrefixes
	}
	if strings.TrimSpace(in.MicrosoftTenant) == "" {
		if strings.TrimSpace(prev.MicrosoftTenant) != "" {
			in.MicrosoftTenant = prev.MicrosoftTenant
		} else {
			in.MicrosoftTenant = defaultMicrosoftTenant
		}
	} else {
		in.MicrosoftTenant = strings.TrimSpace(in.MicrosoftTenant)
	}
	if err := s.setConfig("oauth", in); err != nil {
		return err
	}
	s.oauthConfig = in
	return nil
}

// PublicOAuthFlags 可对外暴露的开关（无密钥）。
type PublicOAuthFlags struct {
	GitHubEnabled     bool `json:"githubEnabled"`
	GiteaEnabled      bool `json:"giteaEnabled"`
	GoogleEnabled     bool `json:"googleEnabled"`
	MicrosoftEnabled  bool `json:"microsoftEnabled"`
	LinuxdoEnabled    bool `json:"linuxdoEnabled"`
}

// PublicOAuthFlags 返回是否启用各提供商。
func (s *Store) PublicOAuthFlags() PublicOAuthFlags {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return PublicOAuthFlags{
		GitHubEnabled:    s.oauthConfig.GitHubEnabled,
		GiteaEnabled:     s.oauthConfig.GiteaEnabled,
		GoogleEnabled:    s.oauthConfig.GoogleEnabled,
		MicrosoftEnabled: s.oauthConfig.MicrosoftEnabled,
		LinuxdoEnabled:   s.oauthConfig.LinuxdoEnabled,
	}
}

// OAuthSignUpAllowed 是否允许用 OAuth 创建新用户（结合注册策略与后台开关）。
func (s *Store) OAuthSignUpAllowed() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.oauthConfig.AllowOAuthSignUpWhenInviteRequired {
		return true
	}
	if s.registrationConfig.RequireInviteCode {
		return false
	}
	return true
}

// GetUserByEmail 主邮箱精确匹配（大小写不敏感，存库一般为小写）。
func (s *Store) GetUserByEmail(email string) (models.UserRecord, bool, error) {
	email = strings.TrimSpace(email)
	if email == "" {
		return models.UserRecord{}, false, nil
	}
	var row DBUser
	result := s.db.First(&row, "LOWER(email) = LOWER(?)", email)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

// GetUserByGitHubID 非空时查询。
func (s *Store) GetUserByGitHubID(id string) (models.UserRecord, bool, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return models.UserRecord{}, false, nil
	}
	var row DBUser
	result := s.db.First(&row, "github_user_id = ?", id)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

// GetUserByGiteaID 非空时查询。
func (s *Store) GetUserByGiteaID(id string) (models.UserRecord, bool, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return models.UserRecord{}, false, nil
	}
	var row DBUser
	result := s.db.First(&row, "gitea_user_id = ?", id)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

// SetUserGitHubID 将 GitHub 用户 ID 绑定到账户；id 被他人占用时返回错误。
func (s *Store) SetUserGitHubID(account, githubID string) error {
	account = strings.TrimSpace(account)
	githubID = strings.TrimSpace(githubID)
	if account == "" || githubID == "" {
		return errors.New("account and id required")
	}
	other, found, err := s.GetUserByGitHubID(githubID)
	if err != nil {
		return err
	}
	if found && !strings.EqualFold(other.Account, account) {
		return errors.New("github account already linked to another user")
	}
	u, found, err := s.GetUser(account)
	if err != nil || !found {
		return errors.New("user not found")
	}
	if strings.TrimSpace(u.GitHubUserID) != "" && u.GitHubUserID != githubID {
		return errors.New("user already has a different GitHub link")
	}
	u.GitHubUserID = githubID
	return s.SaveUser(u)
}

// SetUserGiteaID 将 Gitea 用户 ID 绑定到账户。
func (s *Store) SetUserGiteaID(account, giteaID string) error {
	account = strings.TrimSpace(account)
	giteaID = strings.TrimSpace(giteaID)
	if account == "" || giteaID == "" {
		return errors.New("account and id required")
	}
	other, found, err := s.GetUserByGiteaID(giteaID)
	if err != nil {
		return err
	}
	if found && !strings.EqualFold(other.Account, account) {
		return errors.New("gitea account already linked to another user")
	}
	u, found, err := s.GetUser(account)
	if err != nil || !found {
		return errors.New("user not found")
	}
	if strings.TrimSpace(u.GiteaUserID) != "" && u.GiteaUserID != giteaID {
		return errors.New("user already has a different Gitea link")
	}
	u.GiteaUserID = giteaID
	return s.SaveUser(u)
}

// ClearUserGitHubID 解绑 GitHub。
func (s *Store) ClearUserGitHubID(account string) error {
	u, found, err := s.GetUser(strings.TrimSpace(account))
	if err != nil {
		return err
	}
	if !found {
		return errors.New("user not found")
	}
	u.GitHubUserID = ""
	return s.SaveUser(u)
}

// ClearUserGiteaID 解绑 Gitea。
func (s *Store) ClearUserGiteaID(account string) error {
	u, found, err := s.GetUser(strings.TrimSpace(account))
	if err != nil {
		return err
	}
	if !found {
		return errors.New("user not found")
	}
	u.GiteaUserID = ""
	return s.SaveUser(u)
}

// GetUserByLinuxdoID 非空时查询（LINUX DO Connect 用户主键）。
func (s *Store) GetUserByLinuxdoID(id string) (models.UserRecord, bool, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return models.UserRecord{}, false, nil
	}
	var row DBUser
	result := s.db.First(&row, "linuxdo_user_id = ?", id)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

// SetUserLinuxdoID 绑定 LINUX DO 用户 ID。
func (s *Store) SetUserLinuxdoID(account, linuxdoID string) error {
	account = strings.TrimSpace(account)
	linuxdoID = strings.TrimSpace(linuxdoID)
	if account == "" || linuxdoID == "" {
		return errors.New("account and id required")
	}
	other, found, err := s.GetUserByLinuxdoID(linuxdoID)
	if err != nil {
		return err
	}
	if found && !strings.EqualFold(other.Account, account) {
		return errors.New("linux.do account already linked to another user")
	}
	u, found, err := s.GetUser(account)
	if err != nil || !found {
		return errors.New("user not found")
	}
	if strings.TrimSpace(u.LinuxdoUserID) != "" && u.LinuxdoUserID != linuxdoID {
		return errors.New("user already has a different LINUX DO link")
	}
	u.LinuxdoUserID = linuxdoID
	return s.SaveUser(u)
}

// ClearUserLinuxdoID 解绑 LINUX DO。
func (s *Store) ClearUserLinuxdoID(account string) error {
	u, found, err := s.GetUser(strings.TrimSpace(account))
	if err != nil {
		return err
	}
	if !found {
		return errors.New("user not found")
	}
	u.LinuxdoUserID = ""
	return s.SaveUser(u)
}

// GetUserByGoogleID 非空时查询。
func (s *Store) GetUserByGoogleID(id string) (models.UserRecord, bool, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return models.UserRecord{}, false, nil
	}
	var row DBUser
	result := s.db.First(&row, "google_user_id = ?", id)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

// SetUserGoogleID 将 Google 用户 ID 绑定到账户。
func (s *Store) SetUserGoogleID(account, googleID string) error {
	account = strings.TrimSpace(account)
	googleID = strings.TrimSpace(googleID)
	if account == "" || googleID == "" {
		return errors.New("account and id required")
	}
	other, found, err := s.GetUserByGoogleID(googleID)
	if err != nil {
		return err
	}
	if found && !strings.EqualFold(other.Account, account) {
		return errors.New("google account already linked to another user")
	}
	u, found, err := s.GetUser(account)
	if err != nil || !found {
		return errors.New("user not found")
	}
	if strings.TrimSpace(u.GoogleUserID) != "" && u.GoogleUserID != googleID {
		return errors.New("user already has a different Google link")
	}
	u.GoogleUserID = googleID
	return s.SaveUser(u)
}

// ClearUserGoogleID 解绑 Google。
func (s *Store) ClearUserGoogleID(account string) error {
	u, found, err := s.GetUser(strings.TrimSpace(account))
	if err != nil {
		return err
	}
	if !found {
		return errors.New("user not found")
	}
	u.GoogleUserID = ""
	return s.SaveUser(u)
}

// GetUserByMicrosoftID 非空时查询。
func (s *Store) GetUserByMicrosoftID(id string) (models.UserRecord, bool, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return models.UserRecord{}, false, nil
	}
	var row DBUser
	result := s.db.First(&row, "microsoft_user_id = ?", id)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

// SetUserMicrosoftID 将 Microsoft 用户 ID 绑定到账户。
func (s *Store) SetUserMicrosoftID(account, msID string) error {
	account = strings.TrimSpace(account)
	msID = strings.TrimSpace(msID)
	if account == "" || msID == "" {
		return errors.New("account and id required")
	}
	other, found, err := s.GetUserByMicrosoftID(msID)
	if err != nil {
		return err
	}
	if found && !strings.EqualFold(other.Account, account) {
		return errors.New("microsoft account already linked to another user")
	}
	u, found, err := s.GetUser(account)
	if err != nil || !found {
		return errors.New("user not found")
	}
	if strings.TrimSpace(u.MicrosoftUserID) != "" && u.MicrosoftUserID != msID {
		return errors.New("user already has a different Microsoft link")
	}
	u.MicrosoftUserID = msID
	return s.SaveUser(u)
}

// ClearUserMicrosoftID 解绑 Microsoft。
func (s *Store) ClearUserMicrosoftID(account string) error {
	u, found, err := s.GetUser(strings.TrimSpace(account))
	if err != nil {
		return err
	}
	if !found {
		return errors.New("user not found")
	}
	u.MicrosoftUserID = ""
	return s.SaveUser(u)
}

// ProposeUniqueOAuthAccount 生成未占用的随机自助格式账户名，用于 OAuth 注册。
func (s *Store) ProposeUniqueOAuthAccount() (string, error) {
	for i := 0; i < 48; i++ {
		raw, err := randomOAuthLocalPart(12)
		if err != nil {
			return "", err
		}
		acc := "oauth" + raw
		if err := s.ValidateSelfServiceAccount(acc); err != nil {
			continue
		}
		_, found, err := s.GetUser(acc)
		if err != nil {
			return "", err
		}
		if !found {
			return acc, nil
		}
	}
	return "", errors.New("failed to allocate unique account")
}

// randomOAuthLocalPart 小写字母与数字，长度 n。
func randomOAuthLocalPart(n int) (string, error) {
	if n < 1 {
		n = 8
	}
	const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
	buf := make([]byte, n)
	randBytes, err := generateSecret()
	if err != nil {
		return "", err
	}
	for i := 0; i < n; i++ {
		buf[i] = alphabet[int(randBytes[i%len(randBytes)])%len(alphabet)]
	}
	return string(buf), nil
}

// IsReturnURLAllowed 校验回跳地址是否以白名单中某一前缀开头（需为 http(s) 绝对地址）。
func (c OAuthConfig) IsReturnURLAllowed(raw string) bool {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return false
	}
	u, err := url.Parse(raw)
	if err != nil || u.Scheme == "" || u.Host == "" {
		return false
	}
	if u.Scheme != "https" && u.Scheme != "http" {
		return false
	}
	norm := u.String()
	for _, pfx := range c.AllowedReturnPrefixes {
		pfx = strings.TrimSpace(pfx)
		if pfx == "" {
			continue
		}
		if !strings.HasSuffix(pfx, "/") {
			pfx += "/"
		}
		if strings.HasPrefix(norm, pfx) {
			return true
		}
		base := strings.TrimRight(pfx, "/")
		if norm == base || strings.HasPrefix(norm, base+"/") {
			return true
		}
	}
	return false
}
