package applogic

import (
	"context"
	"fmt"
	"log"
	"strings"

	"aijianzhan/platform/internal/agentcap"
	"aijianzhan/platform/internal/authx"
	"aijianzhan/platform/internal/meta"
	"aijianzhan/platform/internal/smsstore"
	"aijianzhan/platform/internal/svc"
	"aijianzhan/platform/internal/types"
	"aijianzhan/platform/internal/userstore"
)

type AuthLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AuthLogic {
	return &AuthLogic{ctx: ctx, svcCtx: svcCtx}
}

func (l *AuthLogic) IssueToken(req *types.TokenReq) (*types.TokenResp, error) {
	gt := strings.TrimSpace(strings.ToLower(req.GrantType))
	if gt == "client_credentials" || (req.ClientID != "" && req.ClientSecret != "") {
		return l.IssueClientCredentials(req.ClientID, req.ClientSecret)
	}
	want := l.svcCtx.Config.Auth.IssueSecret
	if want == "" {
		want = l.svcCtx.JWT.AccessSecret
	}
	if req.Secret == "" || req.Secret != want {
		return nil, fmt.Errorf("invalid issue secret")
	}
	role := req.Role
	if role == "" {
		role = authx.Role管理员
	}
	return l.issue(req.TenantID, req.UserID, role, "", "", 0)
}

func (l *AuthLogic) Register(req *types.RegisterReq) (*types.TokenResp, error) {
	if l.phoneLoginOnly() {
		return nil, fmt.Errorf("本系统仅支持手机号登录，请联系管理员开通账号并绑定手机")
	}
	if l.svcCtx.Users == nil {
		return nil, fmt.Errorf("user store unavailable")
	}
	u, err := l.svcCtx.Users.Register(l.ctx, req.Username, req.Password, req.DisplayName)
	if err != nil {
		return nil, err
	}
	return l.issueUser(u)
}

func (l *AuthLogic) phoneLoginOnly() bool {
	// 有部署期限控制时，不必强制手机号登录
	if l.svcCtx.Config.License.Enabled {
		return false
	}
	return l.svcCtx.Config.Auth.PhoneLoginOnly
}

// PhoneLoginOnlyPolicy 对外暴露：是否强制仅手机号登录（License 开启时为 false）。
func (l *AuthLogic) PhoneLoginOnlyPolicy() bool {
	return l.phoneLoginOnly()
}

func (l *AuthLogic) Login(req *types.LoginReq) (*types.TokenResp, error) {
	if l.svcCtx.Users == nil {
		return nil, fmt.Errorf("user store unavailable")
	}
	smsCode := strings.TrimSpace(req.SMSCode)
	phone := strings.TrimSpace(req.Phone)
	account := strings.TrimSpace(req.Username)
	if account == "" {
		account = phone
	}

	// 手机号 + 短信验证码
	if smsCode != "" {
		if phone == "" {
			phone = account
		}
		ns, err := userstore.NormalizePhone(phone)
		if err != nil {
			return nil, err
		}
		if l.svcCtx.SMS == nil {
			return nil, fmt.Errorf("短信服务未启用")
		}
		if err := l.svcCtx.SMS.Consume(smsstore.PurposeLogin, ns, smsCode); err != nil {
			return nil, err
		}
		u, err := l.svcCtx.Users.GetByPhone(l.ctx, ns)
		if err != nil {
			return nil, fmt.Errorf("该手机号未绑定账号")
		}
		if u.Status == "disabled" {
			return nil, fmt.Errorf("账号已停用")
		}
		return l.issueUser(u)
	}

	if strings.TrimSpace(req.Password) == "" {
		return nil, fmt.Errorf("请填写密码或短信验证码")
	}

	// 仅手机号登录：必须用手机号+密码
	if l.phoneLoginOnly() {
		loginPhone := phone
		if loginPhone == "" {
			loginPhone = account
		}
		ns, err := userstore.NormalizePhone(loginPhone)
		if err != nil {
			return nil, fmt.Errorf("请使用手机号登录")
		}
		u, err := l.svcCtx.Users.Login(l.ctx, ns, req.Password)
		if err != nil {
			return nil, err
		}
		if err := l.enforceLoginPolicy(u, false); err != nil {
			return nil, err
		}
		return l.issueUser(u)
	}

	// 兼容模式：用户名/手机号 + 密码
	if account == "" {
		return nil, fmt.Errorf("请填写用户名或手机号")
	}
	u, err := l.svcCtx.Users.Login(l.ctx, account, req.Password)
	if err != nil {
		return nil, err
	}
	viaUsername := !userstore.LooksLikePhone(account)
	if err := l.enforceLoginPolicy(u, viaUsername); err != nil {
		return nil, err
	}
	return l.issueUser(u)
}

func (l *AuthLogic) enforceLoginPolicy(u *userstore.User, viaUsername bool) error {
	cfg := l.svcCtx.Config.Auth
	phoneOnly := l.phoneLoginOnly()
	requirePhone := cfg.RequirePhoneBound || phoneOnly
	if requirePhone && strings.TrimSpace(u.Phone) == "" {
		return fmt.Errorf("本部署要求绑定手机号后才能登录，请联系管理员")
	}
	if !viaUsername {
		return nil
	}
	if phoneOnly || cfg.DisableUsernameLoginIfPhoneBound || u.UsernameLoginDisabled {
		if strings.TrimSpace(u.Phone) == "" {
			return fmt.Errorf("已禁用用户名登录，请联系管理员绑定手机号")
		}
		return fmt.Errorf("已禁用用户名登录，请使用手机号+密码或短信验证码")
	}
	return nil
}

type SendSMSResult struct {
	ExpiresIn  int
	RetryAfter int
	DebugCode  string
	Message    string
}

func (l *AuthLogic) SendLoginSMS(phone string) (*SendSMSResult, error) {
	if l.svcCtx.Users == nil || l.svcCtx.SMS == nil {
		return nil, fmt.Errorf("短信服务未启用")
	}
	provider := strings.ToLower(strings.TrimSpace(l.svcCtx.Config.SMS.Provider))
	if provider == "off" || provider == "disabled" {
		return nil, fmt.Errorf("短信登录未开启")
	}
	ns, err := userstore.NormalizePhone(phone)
	if err != nil {
		return nil, err
	}
	if _, err := l.svcCtx.Users.GetByPhone(l.ctx, ns); err != nil {
		return nil, fmt.Errorf("该手机号未绑定账号，请联系管理员绑定手机后再登录")
	}
	code, expiresIn, retryAfter, err := l.svcCtx.SMS.Issue(smsstore.PurposeLogin, ns)
	if err != nil {
		return &SendSMSResult{RetryAfter: retryAfter}, err
	}
	out := &SendSMSResult{
		ExpiresIn: expiresIn,
		Message:   "验证码已发送",
	}
	if provider == "" || provider == "dev" {
		log.Printf("[sms:dev] login code for %s = %s (expires %ds)", ns, code, expiresIn)
		out.DebugCode = code
		out.Message = "开发模式：验证码已写入服务日志（并返回 debug_code）"
	}
	return out, nil
}

func (l *AuthLogic) ChangePassword(oldPassword, newPassword string) error {
	if l.svcCtx.Users == nil {
		return fmt.Errorf("user store unavailable")
	}
	uid := authx.UserID(l.ctx)
	if uid <= 0 {
		return fmt.Errorf("未登录")
	}
	return l.svcCtx.Users.ChangePassword(l.ctx, uid, oldPassword, newPassword)
}

func (l *AuthLogic) BindPhone(phone string) (*userstore.User, error) {
	if l.svcCtx.Users == nil {
		return nil, fmt.Errorf("user store unavailable")
	}
	uid := authx.UserID(l.ctx)
	if uid <= 0 {
		return nil, fmt.Errorf("未登录")
	}
	if l.phoneLoginOnly() && strings.TrimSpace(phone) == "" {
		return nil, fmt.Errorf("本部署仅支持手机号登录，不可解绑手机号")
	}
	return l.svcCtx.Users.BindPhone(l.ctx, uid, phone)
}

func (l *AuthLogic) SetUsernameLoginDisabled(disabled bool) (*userstore.User, error) {
	if l.svcCtx.Users == nil {
		return nil, fmt.Errorf("user store unavailable")
	}
	uid := authx.UserID(l.ctx)
	if uid <= 0 {
		return nil, fmt.Errorf("未登录")
	}
	return l.svcCtx.Users.SetUsernameLoginDisabled(l.ctx, uid, disabled)
}

func (l *AuthLogic) Me() (*userstore.User, error) {
	if l.svcCtx.Users == nil {
		return nil, fmt.Errorf("user store unavailable")
	}
	uid := authx.UserID(l.ctx)
	if uid <= 0 {
		return nil, fmt.Errorf("未登录")
	}
	return l.svcCtx.Users.GetByID(l.ctx, uid)
}

func (l *AuthLogic) issue(tenantID, userID int64, role, username, displayName string, orgUnitID int64) (*types.TokenResp, error) {
	role = authx.NormalizeRole(role)
	token, exp, err := authx.IssueToken(l.svcCtx.JWT, tenantID, userID, role, orgUnitID)
	if err != nil {
		return nil, err
	}
	secret := l.svcCtx.Config.Agent.CapsuleSecret
	if secret == "" {
		secret = l.svcCtx.JWT.AccessSecret
	}
	return &types.TokenResp{
		AccessToken: token,
		TokenType:   "Bearer",
		ExpiresAt:   exp,
		TenantID:    tenantID,
		UserID:      userID,
		Username:    username,
		DisplayName: displayName,
		Role:        role,
		OrgUnitID:   orgUnitID,
		AgentKey:    agentcap.PublicAgentKey(secret, tenantID, userID),
	}, nil
}

type CapsuleLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewCapsuleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CapsuleLogic {
	return &CapsuleLogic{ctx: ctx, svcCtx: svcCtx}
}

func (l *CapsuleLogic) Build(slug string) (*types.CapsuleResp, error) {
	tenantID := authx.TenantID(l.ctx)
	userID := authx.UserID(l.ctx)
	if authx.Role(l.ctx) == authx.RoleAgent {
		if l.svcCtx.Agents == nil {
			return nil, fmt.Errorf("agent store unavailable")
		}
		ok, err := l.svcCtx.Agents.HasAppAccess(l.ctx, authx.AgentID(l.ctx), slug)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, fmt.Errorf("app not granted to agent")
		}
	}
	app, err := l.svcCtx.Meta.GetBySlug(l.ctx, tenantID, slug)
	if err != nil {
		return nil, err
	}
	if app.Status != meta.StatusPublished {
		return nil, fmt.Errorf("app not published")
	}
	base := l.svcCtx.Config.PublicBaseURL
	if base == "" {
		base = fmt.Sprintf("http://127.0.0.1:%d", l.svcCtx.Config.Port)
	}
	desc := &agentcap.Descriptor{
		Version:           "1",
		BaseURL:           strings.TrimRight(base, "/"),
		AppSlug:           slug,
		TenantHint:        fmt.Sprintf("t%d", tenantID),
		SchemaName:        app.SchemaName,
		BlueprintRevision: app.Blueprint.Revision(),
		Auth:              agentcap.AuthSpec{Type: "bearer_jwt", Header: "Authorization"},
		Notes:             "Decrypt with agent_key from /auth/token. Never expose plaintext API map in UI.",
	}
	for _, r := range app.Blueprint.Apis.Resources {
		path := r.Path
		if !strings.HasPrefix(path, "/") {
			path = "/" + path
		}
		full := fmt.Sprintf("/api/v1/apps/%s%s", slug, path)
		methods := make([]string, 0, len(r.Operations))
		for _, op := range r.Operations {
			switch op {
			case "list", "get", "export":
				methods = append(methods, "GET")
			case "create", "import":
				methods = append(methods, "POST")
			case "update":
				methods = append(methods, "PUT")
			case "delete":
				methods = append(methods, "DELETE")
			}
		}
		var entityFields []agentcap.FieldSpec
		pk := "id"
		table := ""
		for _, e := range app.Blueprint.Entities {
			if e.Name != r.Entity {
				continue
			}
			pk = e.PrimaryKey
			table = e.Table
			for _, f := range e.Fields {
				entityFields = append(entityFields, agentcap.FieldSpec{Name: f.Name, Type: f.Type})
			}
		}
		filters, sorts := []string{}, []string{}
		if r.List != nil {
			filters = r.List.AllowedFilters
			sorts = r.List.AllowedSorts
		}
		name := strings.TrimPrefix(path, "/")
		desc.Resources = append(desc.Resources, agentcap.ResourceSpec{
			Name:       name,
			Path:       full,
			Methods:    uniqStrings(methods),
			Filters:    filters,
			Sorts:      sorts,
			Fields:     entityFields,
			PrimaryKey: pk,
			Table:      app.SchemaName + "." + table,
		})
	}

	secret := l.svcCtx.Config.Agent.CapsuleSecret
	if secret == "" {
		secret = l.svcCtx.JWT.AccessSecret
	}
	key := agentcap.DeriveKey(secret, tenantID, userID)
	capsule, err := agentcap.Encrypt(key, desc)
	if err != nil {
		return nil, err
	}
	return &types.CapsuleResp{
		Capsule: capsule,
		Format:  agentcap.Prefix,
		Hint:    "仅智能体使用 agent_key 解密；前端只展示密文",
	}, nil
}

func uniqStrings(in []string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0, len(in))
	for _, s := range in {
		if _, ok := seen[s]; ok {
			continue
		}
		seen[s] = struct{}{}
		out = append(out, s)
	}
	return out
}
