package identity

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"time"

	"gorm.io/gorm"

	"qycw/go-server/internal/plugins"
	redisstore "qycw/go-server/internal/redis"
)

var pluginIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)

func validatePluginID(id string) bool {
	return pluginIDPattern.MatchString(id) && len(id) <= 64
}

func oauthStateKey(state string) string {
	return redisstore.Key("oauth", "state", state)
}

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

// OAuthProfile is the normalized user info returned by auth plugins.
type OAuthProfile struct {
	ProviderUserID string         `json:"provider_user_id"`
	Username       string         `json:"username"`
	Email          string         `json:"email"`
	Nickname       string         `json:"nickname"`
	AvatarURL      string         `json:"avatar_url"`
	Raw            map[string]any `json:"raw"`
}

func (s *Service) ListAuthProviders(ctx context.Context) ([]map[string]any, error) {
	if s.plugins == nil {
		return []map[string]any{}, nil
	}
	manifests, err := s.plugins.List(ctx, plugins.CapabilityAuth)
	if err != nil {
		return []map[string]any{}, nil
	}
	out := make([]map[string]any, 0, len(manifests))
	for _, m := range manifests {
		enabled, err := s.authPluginEnabled(ctx, m.ID)
		if err != nil {
			return nil, err
		}
		if !enabled {
			continue
		}
		out = append(out, map[string]any{
			"id":   m.ID,
			"name": m.Name,
		})
	}
	return out, nil
}

func (s *Service) GetOAuthURL(ctx context.Context, pluginID, redirectURI string) (map[string]any, error) {
	if !validatePluginID(pluginID) {
		return nil, BadRequest("无效的 plugin_id")
	}
	if s.redis == nil {
		return nil, ServiceUnavailable("Redis 未启用")
	}
	if s.plugins == nil {
		return nil, ServiceUnavailable("插件服务未启用")
	}
	enabled, err := s.authPluginEnabled(ctx, pluginID)
	if err != nil {
		return nil, err
	}
	if !enabled {
		return nil, BadRequest("OAuth 登录方式未启用")
	}
	state, err := newOAuthState()
	if err != nil {
		return nil, fmt.Errorf("generate oauth state: %w", err)
	}
	config, _ := s.plugins.Config(ctx, pluginID)
	params := map[string]any{
		"mode":         "get_url",
		"state":        state,
		"redirect_uri": redirectURI,
		"config":       config,
	}
	var result map[string]any
	if err := s.plugins.Call(ctx, pluginID, plugins.CapabilityAuth, plugins.MethodAuthGetOAuthURL, params, &result); err != nil {
		return nil, err
	}
	stateVal := fmt.Sprintf(`{"plugin_id":%q,"redirect_uri":%q}`, pluginID, redirectURI)
	if err := s.redis.Set(ctx, oauthStateKey(state), stateVal, 5*time.Minute).Err(); err != nil {
		return nil, fmt.Errorf("store oauth state: %w", err)
	}
	result["state"] = state
	return result, nil
}

func (s *Service) authPluginEnabled(ctx context.Context, pluginID string) (bool, error) {
	if s.db == nil {
		return false, ServiceUnavailable("数据库未启用")
	}
	var row struct {
		Value []byte `gorm:"column:value"`
	}
	err := s.db.WithContext(ctx).Table("system_settings").Select("value").Where("`key` = ?", "auth.enabled_plugins").Take(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	var configured string
	if err := json.Unmarshal(row.Value, &configured); err != nil {
		configured = strings.Trim(strings.TrimSpace(string(row.Value)), `"`)
	}
	for _, item := range strings.Split(configured, ",") {
		if strings.TrimSpace(item) == pluginID {
			return true, nil
		}
	}
	return false, nil
}

func (s *Service) ExchangeOAuthCode(ctx context.Context, pluginID, code, state, redirectURI string) (*OAuthProfile, error) {
	if !validatePluginID(pluginID) {
		return nil, BadRequest("无效的 plugin_id")
	}
	if s.redis == nil {
		return nil, ServiceUnavailable("Redis 未启用")
	}
	if s.plugins == nil {
		return nil, ServiceUnavailable("插件服务未启用")
	}
	enabled, err := s.authPluginEnabled(ctx, pluginID)
	if err != nil {
		return nil, err
	}
	if !enabled {
		return nil, BadRequest("OAuth 登录方式未启用")
	}
	key := oauthStateKey(state)
	if _, err := s.redis.GetDel(ctx, key).Result(); err != nil {
		return nil, BadRequest("OAuth state 无效或已过期")
	}
	config, _ := s.plugins.Config(ctx, pluginID)
	params := map[string]any{
		"mode":         "exchange_code",
		"code":         code,
		"state":        state,
		"redirect_uri": redirectURI,
		"config":       config,
	}
	var profile OAuthProfile
	if err := s.plugins.Call(ctx, pluginID, plugins.CapabilityAuth, plugins.MethodAuthExchangeCode, params, &profile); err != nil {
		return nil, err
	}
	if strings.TrimSpace(profile.ProviderUserID) == "" {
		return nil, fmt.Errorf("auth plugin returned empty provider_user_id")
	}
	return &profile, nil
}

func (s *Service) FindOrCreateOAuthUser(ctx context.Context, pluginID string, profile *OAuthProfile) (*User, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var conn UserOAuthConnection
	err := s.db.WithContext(ctx).
		Where("plugin_id = ? AND provider_uid = ?", pluginID, profile.ProviderUserID).
		First(&conn).Error
	if err == nil {
		var user User
		if err := s.db.WithContext(ctx).First(&user, conn.UserID).Error; err != nil {
			return nil, fmt.Errorf("load oauth user: %w", err)
		}
		return &user, nil
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fmt.Errorf("lookup oauth connection: %w", err)
	}

	var user User
	if profile.Email != "" {
		s.db.WithContext(ctx).Where("email = ?", profile.Email).First(&user)
	}
	if user.ID == 0 {
		uid := profile.ProviderUserID
		if len(uid) > 16 {
			uid = uid[:16]
		}
		username := pluginID + "_" + uid
		now := time.Now().UTC()
		user = User{Username: username, Role: "user", Status: "active", CreatedAt: now, UpdatedAt: now}
		if profile.Email != "" {
			e := profile.Email
			user.Email = &e
		}
		if err := s.db.WithContext(ctx).Create(&user).Error; err != nil {
			return nil, fmt.Errorf("create oauth user: %w", err)
		}
	}

	now := time.Now().UTC()
	newConn := UserOAuthConnection{UserID: user.ID, PluginID: pluginID, ProviderUID: profile.ProviderUserID, CreatedAt: now, UpdatedAt: now}
	if err := s.db.WithContext(ctx).Create(&newConn).Error; err != nil {
		return nil, fmt.Errorf("create oauth connection: %w", err)
	}
	return &user, nil
}

func (s *Service) ListOAuthConnections(ctx context.Context, userID int64) ([]map[string]any, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var conns []UserOAuthConnection
	if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&conns).Error; err != nil {
		return nil, err
	}
	out := make([]map[string]any, 0, len(conns))
	for _, c := range conns {
		out = append(out, map[string]any{"plugin_id": c.PluginID, "created_at": c.CreatedAt})
	}
	return out, nil
}

func (s *Service) UnbindOAuth(ctx context.Context, userID int64, pluginID string) error {
	if !validatePluginID(pluginID) {
		return BadRequest("无效的 plugin_id")
	}
	if err := s.requireDB(); err != nil {
		return err
	}
	var user User
	if err := s.db.WithContext(ctx).First(&user, userID).Error; err != nil {
		return err
	}
	if user.HashedPassword == nil {
		var count int64
		s.db.WithContext(ctx).Model(&UserOAuthConnection{}).Where("user_id = ?", userID).Count(&count)
		if count <= 1 {
			return BadRequest("无法解绑：账号没有密码且仅剩一种登录方式")
		}
	}
	return s.db.WithContext(ctx).Where("user_id = ? AND plugin_id = ?", userID, pluginID).Delete(&UserOAuthConnection{}).Error
}

func (s *Service) BindOAuth(ctx context.Context, userID int64, pluginID string, profile *OAuthProfile) error {
	if !validatePluginID(pluginID) {
		return BadRequest("无效的 plugin_id")
	}
	if err := s.requireDB(); err != nil {
		return err
	}
	now := time.Now().UTC()
	conn := UserOAuthConnection{UserID: userID, PluginID: pluginID, ProviderUID: profile.ProviderUserID, CreatedAt: now, UpdatedAt: now}
	return s.db.WithContext(ctx).
		Where("plugin_id = ? AND provider_uid = ?", pluginID, profile.ProviderUserID).
		FirstOrCreate(&conn).Error
}

func (s *Service) IssueOAuthTokenPair(ctx context.Context, user *User) (map[string]any, error) {
	accessToken, _, err := s.tokens.Create(fmt.Sprint(user.ID), user.Role, "access")
	if err != nil {
		return nil, err
	}
	refreshToken, jti, err := s.tokens.Create(fmt.Sprint(user.ID), user.Role, "refresh")
	if err != nil {
		return nil, err
	}
	if err := s.addRefresh(ctx, user.ID, jti); err != nil {
		return nil, err
	}
	return map[string]any{
		"access_token":  accessToken,
		"refresh_token": refreshToken,
		"token_type":    "Bearer",
	}, nil
}
