package oci

import (
	"context"
	"errors"
	"fmt"

	"github.com/oracle/oci-go-sdk/v65/common"
	"github.com/oracle/oci-go-sdk/v65/identity"
	"github.com/oracle/oci-go-sdk/v65/identitydomains"
)

// notificationSettingID 是 Identity Domain 通知设置的固定资源 ID。
const notificationSettingID = "NotificationSettings"

// NotificationRecipients 是域通知收件人设置：test mode 开启后
// 域内全部通知只发给 recipients 列表。
type NotificationRecipients struct {
	Recipients      []string `json:"recipients"`
	TestModeEnabled bool     `json:"testModeEnabled"`
}

// PasswordPolicyInfo 是一条域密码策略的关键字段。
type PasswordPolicyInfo struct {
	ID                    string `json:"id"`
	Name                  string `json:"name"`
	Priority              int    `json:"priority"`
	PasswordStrength      string `json:"passwordStrength"`
	PasswordExpiresAfter  *int   `json:"passwordExpiresAfter,omitempty"`
	MinLength             *int   `json:"minLength,omitempty"`
	NumPasswordsInHistory *int   `json:"numPasswordsInHistory,omitempty"`
}

// UpdatePasswordPolicyInput 是密码策略修改项；nil 字段不修改。
// PasswordExpiresAfter 为 0 表示密码永不过期；
// NumPasswordsInHistory 为 0 表示可复用以前的密码（不记历史）。
type UpdatePasswordPolicyInput struct {
	PasswordExpiresAfter  *int
	MinLength             *int
	NumPasswordsInHistory *int
}

// IdentitySettingInfo 是域身份设置里本面板关心的开关。
type IdentitySettingInfo struct {
	ID string `json:"id"`
	// 用户需要提供主电子邮件地址：开启后创建用户 / JIT 预配必须带邮箱
	PrimaryEmailRequired bool `json:"primaryEmailRequired"`
}

// IdentityDomain 是租户下一个 ACTIVE 身份域的列表视图;
// URL 仅供后端解析 SCIM 端点,不下发给前端(防面板被引导签名任意地址)。
type IdentityDomain struct {
	ID          string `json:"id"`
	DisplayName string `json:"displayName"`
	URL         string `json:"-"`
	HomeRegion  string `json:"homeRegion"`
	Type        string `json:"type"`
	LicenseType string `json:"licenseType"`
}

// ListIdentityDomains 实现 Client:列出租户全部 ACTIVE 身份域。
// 无域老租户返回空列表(非错误),调用方据此回退经典 IAM 路径。
func (c *RealClient) ListIdentityDomains(ctx context.Context, cred Credentials, region string) ([]IdentityDomain, error) {
	items, err := c.listDomainSummaries(ctx, cred, region)
	if err != nil {
		return nil, err
	}
	out := make([]IdentityDomain, 0, len(items))
	for _, d := range items {
		if d.LifecycleState != identity.DomainLifecycleStateActive {
			continue
		}
		out = append(out, IdentityDomain{
			ID:          deref(d.Id),
			DisplayName: deref(d.DisplayName),
			URL:         deref(d.Url),
			HomeRegion:  deref(d.HomeRegion),
			Type:        string(d.Type),
			LicenseType: deref(d.LicenseType),
		})
	}
	return out, nil
}

// listDomainSummaries 拉取租户全部域(数量极少,仍按游标翻全以防万一)。
func (c *RealClient) listDomainSummaries(ctx context.Context, cred Credentials, region string) ([]identity.DomainSummary, error) {
	ic, err := c.identityClientAt(cred, region)
	if err != nil {
		return nil, err
	}
	var items []identity.DomainSummary
	var page *string
	for {
		resp, err := ic.ListDomains(ctx, identity.ListDomainsRequest{CompartmentId: &cred.TenancyOCID, Page: page})
		if err != nil {
			return nil, fmt.Errorf("list identity domains: %w", err)
		}
		items = append(items, resp.Items...)
		if resp.OpcNextPage == nil {
			return items, nil
		}
		page = resp.OpcNextPage
	}
}

// domainURL 解析身份域的 endpoint URL:domainID 非空按 OCID 精确匹配
// (URL 只能来自租户自己的域列表,不信任外部输入),为空回退 Default 优先。
func (c *RealClient) domainURL(ctx context.Context, cred Credentials, region, domainID string) (string, error) {
	items, err := c.listDomainSummaries(ctx, cred, region)
	if err != nil {
		return "", err
	}
	url := pickDomainURL(items, domainID)
	if url == "" {
		if domainID != "" {
			return "", fmt.Errorf("identity domain %s not found or inactive", domainID)
		}
		return "", fmt.Errorf("no active identity domain found")
	}
	return url, nil
}

// domainsClient 解析身份域(domainID 为空取 Default)并构造 SCIM 客户端。
func (c *RealClient) domainsClient(ctx context.Context, cred Credentials, region, domainID string) (identitydomains.IdentityDomainsClient, error) {
	url, err := c.domainURL(ctx, cred, region, domainID)
	if err != nil {
		return identitydomains.IdentityDomainsClient{}, err
	}
	dc, err := identitydomains.NewIdentityDomainsClientWithConfigurationProvider(provider(cred), url)
	if err != nil {
		return dc, fmt.Errorf("new identity domains client: %w", err)
	}
	applyProxy(&dc.BaseClient, cred)
	// IDCS 错误体是 SCIM 格式，改写后 SDK 才能解析出真实错误消息
	dc.HTTPClient = scimErrorDispatcher{base: dc.HTTPClient}
	return dc, nil
}

// pickDomainURL 在 ACTIVE 域中选取:domainID 非空按 OCID 匹配;
// 为空优先名为 Default 的域,否则取第一个 ACTIVE 的域。
func pickDomainURL(domains []identity.DomainSummary, domainID string) string {
	fallback := ""
	for _, d := range domains {
		if d.LifecycleState != identity.DomainLifecycleStateActive {
			continue
		}
		if domainID != "" {
			if deref(d.Id) == domainID {
				return deref(d.Url)
			}
			continue
		}
		if deref(d.DisplayName) == "Default" {
			return deref(d.Url)
		}
		if fallback == "" {
			fallback = deref(d.Url)
		}
	}
	return fallback
}

// GetNotificationRecipients 实现 Client：查询域通知收件人设置。
func (c *RealClient) GetNotificationRecipients(ctx context.Context, cred Credentials, region, domainID string) (NotificationRecipients, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return NotificationRecipients{}, err
	}
	setting, err := getNotificationSetting(ctx, dc)
	if err != nil {
		return NotificationRecipients{}, err
	}
	return NotificationRecipients{
		Recipients:      orEmpty(setting.TestRecipients),
		TestModeEnabled: setting.TestModeEnabled != nil && *setting.TestModeEnabled,
	}, nil
}

// UpdateNotificationRecipients 实现 Client：把域通知改为只发给指定收件人；
// emails 为空时关闭 test mode 恢复默认发送。其余字段原样保留。
func (c *RealClient) UpdateNotificationRecipients(ctx context.Context, cred Credentials, region, domainID string, emails []string) (NotificationRecipients, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return NotificationRecipients{}, err
	}
	setting, err := getNotificationSetting(ctx, dc)
	if err != nil {
		return NotificationRecipients{}, err
	}
	enabled := len(emails) > 0
	setting.TestRecipients = emails
	setting.TestModeEnabled = &enabled
	id := notificationSettingID
	resp, err := dc.PutNotificationSetting(ctx, identitydomains.PutNotificationSettingRequest{
		NotificationSettingId: &id,
		NotificationSetting:   setting,
	})
	if err != nil {
		return NotificationRecipients{}, fmt.Errorf("put notification setting: %w", err)
	}
	return NotificationRecipients{
		Recipients:      orEmpty(resp.TestRecipients),
		TestModeEnabled: resp.TestModeEnabled != nil && *resp.TestModeEnabled,
	}, nil
}

func getNotificationSetting(ctx context.Context, dc identitydomains.IdentityDomainsClient) (identitydomains.NotificationSetting, error) {
	id := notificationSettingID
	resp, err := dc.GetNotificationSetting(ctx, identitydomains.GetNotificationSettingRequest{
		NotificationSettingId: &id,
	})
	if err != nil {
		return identitydomains.NotificationSetting{}, fmt.Errorf("get notification setting: %w", err)
	}
	return resp.NotificationSetting, nil
}

// ListPasswordPolicies 实现 Client：列出域内全部密码策略。
func (c *RealClient) ListPasswordPolicies(ctx context.Context, cred Credentials, region, domainID string) ([]PasswordPolicyInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return nil, err
	}
	resp, err := dc.ListPasswordPolicies(ctx, identitydomains.ListPasswordPoliciesRequest{})
	if err != nil {
		return nil, fmt.Errorf("list password policies: %w", err)
	}
	policies := make([]PasswordPolicyInfo, 0, len(resp.Resources))
	for _, p := range resp.Resources {
		policies = append(policies, toPasswordPolicyInfo(p))
	}
	return policies, nil
}

// UpdatePasswordPolicy 实现 Client：以 SCIM Patch 修改密码策略的指定字段。
// 仅 Custom 强度的策略可修改，Simple/Standard 内置策略 OCI 返回 403。
func (c *RealClient) UpdatePasswordPolicy(ctx context.Context, cred Credentials, region, domainID, policyID string, in UpdatePasswordPolicyInput) (PasswordPolicyInfo, error) {
	ops := buildPolicyPatchOps(in)
	if len(ops) == 0 {
		return PasswordPolicyInfo{}, fmt.Errorf("update password policy: nothing to update")
	}
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return PasswordPolicyInfo{}, err
	}
	resp, err := dc.PatchPasswordPolicy(ctx, identitydomains.PatchPasswordPolicyRequest{
		PasswordPolicyId: &policyID,
		PatchOp: identitydomains.PatchOp{
			Schemas:    []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
			Operations: ops,
		},
	})
	if err != nil {
		if isForbidden(err) {
			return PasswordPolicyInfo{}, fmt.Errorf("patch password policy %s: only Custom-strength policies are editable: %w", policyID, err)
		}
		return PasswordPolicyInfo{}, fmt.Errorf("patch password policy %s: %w", policyID, err)
	}
	return toPasswordPolicyInfo(resp.PasswordPolicy), nil
}

// isForbidden 判断服务端返回 403（内置只读资源等场景）。
func isForbidden(err error) bool {
	var svcErr common.ServiceError
	return errors.As(err, &svcErr) && svcErr.GetHTTPStatusCode() == 403
}

func buildPolicyPatchOps(in UpdatePasswordPolicyInput) []identitydomains.Operations {
	var ops []identitydomains.Operations
	if in.PasswordExpiresAfter != nil {
		ops = append(ops, intOrRemoveOp("passwordExpiresAfter", *in.PasswordExpiresAfter))
	}
	if in.MinLength != nil {
		ops = append(ops, replaceOp("minLength", *in.MinLength))
	}
	if in.NumPasswordsInHistory != nil {
		ops = append(ops, intOrRemoveOp("numPasswordsInHistory", *in.NumPasswordsInHistory))
	}
	return ops
}

// intOrRemoveOp 值为 0 时移除属性（IDCS 语义 null = 永不过期 / 可复用密码；
// numPasswordsInHistory 合法范围 1-500，replace 0 会被拒），否则 replace。
func intOrRemoveOp(path string, v int) identitydomains.Operations {
	if v == 0 {
		return removeOp(path)
	}
	return replaceOp(path, v)
}

func removeOp(path string) identitydomains.Operations {
	return identitydomains.Operations{
		Op:   identitydomains.OperationsOpRemove,
		Path: &path,
	}
}

// GetIdentitySetting 实现 Client：读取域身份设置（IdentitySettings 单例资源）。
func (c *RealClient) GetIdentitySetting(ctx context.Context, cred Credentials, region, domainID string) (IdentitySettingInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return IdentitySettingInfo{}, err
	}
	resp, err := dc.ListIdentitySettings(ctx, identitydomains.ListIdentitySettingsRequest{})
	if err != nil {
		return IdentitySettingInfo{}, fmt.Errorf("list identity settings: %w", err)
	}
	if len(resp.Resources) == 0 {
		return IdentitySettingInfo{}, fmt.Errorf("identity settings resource not found")
	}
	s := resp.Resources[0]
	info := IdentitySettingInfo{ID: deref(s.Id)}
	if s.PrimaryEmailRequired != nil {
		info.PrimaryEmailRequired = *s.PrimaryEmailRequired
	}
	return info, nil
}

// UpdateIdentitySetting 实现 Client：修改「用户需要提供主电子邮件地址」开关。
func (c *RealClient) UpdateIdentitySetting(ctx context.Context, cred Credentials, region, domainID string, primaryEmailRequired bool) (IdentitySettingInfo, error) {
	current, err := c.GetIdentitySetting(ctx, cred, region, domainID)
	if err != nil {
		return IdentitySettingInfo{}, err
	}
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return IdentitySettingInfo{}, err
	}
	resp, err := dc.PatchIdentitySetting(ctx, identitydomains.PatchIdentitySettingRequest{
		IdentitySettingId: &current.ID,
		PatchOp: identitydomains.PatchOp{
			Schemas:    []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
			Operations: []identitydomains.Operations{replaceOp("primaryEmailRequired", primaryEmailRequired)},
		},
	})
	if err != nil {
		return IdentitySettingInfo{}, fmt.Errorf("patch identity settings: %w", err)
	}
	info := IdentitySettingInfo{ID: deref(resp.IdentitySetting.Id)}
	if resp.IdentitySetting.PrimaryEmailRequired != nil {
		info.PrimaryEmailRequired = *resp.IdentitySetting.PrimaryEmailRequired
	}
	return info, nil
}

func replaceOp(path string, value interface{}) identitydomains.Operations {
	return identitydomains.Operations{
		Op:    identitydomains.OperationsOpReplace,
		Path:  &path,
		Value: &value,
	}
}

func toPasswordPolicyInfo(p identitydomains.PasswordPolicy) PasswordPolicyInfo {
	info := PasswordPolicyInfo{
		ID:                    deref(p.Id),
		Name:                  deref(p.Name),
		PasswordStrength:      string(p.PasswordStrength),
		PasswordExpiresAfter:  p.PasswordExpiresAfter,
		MinLength:             p.MinLength,
		NumPasswordsInHistory: p.NumPasswordsInHistory,
	}
	if p.Priority != nil {
		info.Priority = *p.Priority
	}
	return info
}
