package oci

import (
	"context"
	"encoding/json"
	"fmt"
	"sort"
	"strings"

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

// ociConsolePolicyID 是预置 sign-on 策略「Security Policy for OCI Console」的固定 ID。
const ociConsolePolicyID = "OciConsolePolicy"

// scimPatchSchema 是 SCIM PatchOp 的 schema。
const scimPatchSchema = "urn:ietf:params:scim:api:messages:2.0:PatchOp"

// SignOnRuleInfo 是 sign-on 策略中一条规则的关键字段。
type SignOnRuleInfo struct {
	ID                   string `json:"id"`
	Name                 string `json:"name"`
	Sequence             int    `json:"sequence"`
	AuthenticationFactor string `json:"authenticationFactor"`
	ConditionAttribute   string `json:"conditionAttribute,omitempty"`
	ConditionValue       string `json:"conditionValue,omitempty"`
	BuiltIn              bool   `json:"builtIn"`
}

// ListConsoleSignOnRules 实现 Client：按优先级列出 OCI Console sign-on 策略的规则。
func (c *RealClient) ListConsoleSignOnRules(ctx context.Context, cred Credentials, region, domainID string) ([]SignOnRuleInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return nil, err
	}
	refs, err := consolePolicyRules(ctx, dc)
	if err != nil {
		return nil, err
	}
	rules := make([]SignOnRuleInfo, 0, len(refs))
	for _, ref := range refs {
		info, err := loadSignOnRule(ctx, dc, deref(ref.Value), ref.Sequence)
		if err != nil {
			return nil, err
		}
		rules = append(rules, info)
	}
	sort.Slice(rules, func(i, j int) bool { return rules[i].Sequence < rules[j].Sequence })
	return rules, nil
}

func consolePolicyRules(ctx context.Context, dc identitydomains.IdentityDomainsClient) ([]identitydomains.PolicyRules, error) {
	resp, err := dc.GetPolicy(ctx, identitydomains.GetPolicyRequest{
		PolicyId:   common.String(ociConsolePolicyID),
		Attributes: common.String("id,name,rules"),
	})
	if err != nil {
		return nil, fmt.Errorf("get console sign-on policy: %w", err)
	}
	return resp.Rules, nil
}

func loadSignOnRule(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string, seq *int) (SignOnRuleInfo, error) {
	resp, err := dc.GetRule(ctx, identitydomains.GetRuleRequest{RuleId: &ruleID})
	if err != nil {
		return SignOnRuleInfo{}, fmt.Errorf("get rule %s: %w", ruleID, err)
	}
	info := SignOnRuleInfo{
		ID:      ruleID,
		Name:    deref(resp.Name),
		BuiltIn: strings.HasPrefix(ruleID, "OciConsole"),
	}
	if seq != nil {
		info.Sequence = *seq
	}
	for _, r := range resp.Return {
		if deref(r.Name) == "authenticationFactor" {
			info.AuthenticationFactor = deref(r.Value)
		}
	}
	fillRuleCondition(ctx, dc, resp.Rule, &info)
	return info, nil
}

// fillRuleCondition 尽力回填规则条件（仅解析直接引用 Condition 的规则）。
func fillRuleCondition(ctx context.Context, dc identitydomains.IdentityDomainsClient, rule identitydomains.Rule, info *SignOnRuleInfo) {
	cg := rule.ConditionGroup
	if cg == nil || cg.Type != identitydomains.RuleConditionGroupTypeCondition || cg.Value == nil {
		return
	}
	resp, err := dc.GetCondition(ctx, identitydomains.GetConditionRequest{ConditionId: cg.Value})
	if err != nil {
		return
	}
	info.ConditionAttribute = deref(resp.AttributeName)
	info.ConditionValue = deref(resp.AttributeValue)
}

// CreateMfaExemptionRule 实现 Client：为指定 IdP 创建免 MFA sign-on 规则并置顶。
// 规则语义与控制台一致：subject.authenticatedBy in [idpID] → authenticationFactor=IDP。
func (c *RealClient) CreateMfaExemptionRule(ctx context.Context, cred Credentials, region, domainID, idpID, ruleName string) (SignOnRuleInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return SignOnRuleInfo{}, err
	}
	condID, err := createIdpCondition(ctx, dc, idpID, ruleName)
	if err != nil {
		return SignOnRuleInfo{}, err
	}
	ruleID, err := createExemptionRule(ctx, dc, condID, ruleName)
	if err != nil {
		deleteConditionQuiet(ctx, dc, condID)
		return SignOnRuleInfo{}, err
	}
	if err := prependRuleToPolicy(ctx, dc, ruleID); err != nil {
		deleteRuleQuiet(ctx, dc, ruleID)
		deleteConditionQuiet(ctx, dc, condID)
		return SignOnRuleInfo{}, err
	}
	return SignOnRuleInfo{
		ID: ruleID, Name: ruleName, Sequence: 1,
		AuthenticationFactor: "IDP",
		ConditionAttribute:   "subject.authenticatedBy",
		ConditionValue:       jsonList(idpID),
	}, nil
}

func jsonList(items ...string) string {
	b, _ := json.Marshal(items)
	return string(b)
}

func createIdpCondition(ctx context.Context, dc identitydomains.IdentityDomainsClient, idpID, name string) (string, error) {
	value := jsonList(idpID)
	resp, err := dc.CreateCondition(ctx, identitydomains.CreateConditionRequest{
		Condition: identitydomains.Condition{
			Schemas:        []string{"urn:ietf:params:scim:schemas:oracle:idcs:Condition"},
			Name:           common.String(name + "-condition"),
			AttributeName:  common.String("subject.authenticatedBy"),
			Operator:       identitydomains.ConditionOperatorIn,
			AttributeValue: &value,
		},
	})
	if err != nil {
		return "", fmt.Errorf("create idp condition: %w", err)
	}
	return deref(resp.Id), nil
}

func createExemptionRule(ctx context.Context, dc identitydomains.IdentityDomainsClient, condID, name string) (string, error) {
	resp, err := dc.CreateRule(ctx, identitydomains.CreateRuleRequest{
		Rule: identitydomains.Rule{
			Schemas:    []string{"urn:ietf:params:scim:schemas:oracle:idcs:Rule"},
			Name:       &name,
			PolicyType: &identitydomains.RulePolicyType{Value: common.String("SignOn")},
			ConditionGroup: &identitydomains.RuleConditionGroup{
				Type:  identitydomains.RuleConditionGroupTypeCondition,
				Value: &condID,
			},
			Return: []identitydomains.RuleReturn{
				{Name: common.String("effect"), Value: common.String("ALLOW")},
				{Name: common.String("reAuthenticate"), Value: common.String("false")},
				{Name: common.String("authenticationFactor"), Value: common.String("IDP")},
			},
		},
	})
	if err != nil {
		return "", fmt.Errorf("create exemption rule: %w", err)
	}
	return deref(resp.Id), nil
}

// prependRuleToPolicy 把规则插到策略首位，其余规则优先级顺延。
func prependRuleToPolicy(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string) error {
	existing, err := consolePolicyRules(ctx, dc)
	if err != nil {
		return err
	}
	rules := []interface{}{map[string]interface{}{"value": ruleID, "sequence": 1}}
	for i, r := range sortedRules(existing) {
		rules = append(rules, map[string]interface{}{"value": deref(r.Value), "sequence": i + 2})
	}
	return patchPolicyRules(ctx, dc, rules)
}

// removeRuleFromPolicy 从策略移除规则，其余规则优先级从 1 重排。
func removeRuleFromPolicy(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string) error {
	existing, err := consolePolicyRules(ctx, dc)
	if err != nil {
		return err
	}
	rules := make([]interface{}, 0, len(existing))
	for _, r := range sortedRules(existing) {
		if deref(r.Value) == ruleID {
			continue
		}
		rules = append(rules, map[string]interface{}{"value": deref(r.Value), "sequence": len(rules) + 1})
	}
	if len(rules) == len(existing) {
		return nil
	}
	return patchPolicyRules(ctx, dc, rules)
}

func sortedRules(rules []identitydomains.PolicyRules) []identitydomains.PolicyRules {
	out := append([]identitydomains.PolicyRules(nil), rules...)
	sort.Slice(out, func(i, j int) bool {
		si, sj := 0, 0
		if out[i].Sequence != nil {
			si = *out[i].Sequence
		}
		if out[j].Sequence != nil {
			sj = *out[j].Sequence
		}
		return si < sj
	})
	return out
}

func patchPolicyRules(ctx context.Context, dc identitydomains.IdentityDomainsClient, rules []interface{}) error {
	var value interface{} = rules
	_, err := dc.PatchPolicy(ctx, identitydomains.PatchPolicyRequest{
		PolicyId: common.String(ociConsolePolicyID),
		PatchOp: identitydomains.PatchOp{
			Schemas: []string{scimPatchSchema},
			Operations: []identitydomains.Operations{{
				Op:    identitydomains.OperationsOpReplace,
				Path:  common.String("rules"),
				Value: &value,
			}},
		},
	})
	if err != nil {
		return fmt.Errorf("patch console sign-on policy rules: %w", err)
	}
	return nil
}

// DeleteMfaExemptionRule 实现 Client：删除免 MFA 规则并连带清理其条件。
// 拒绝删除 Oracle 预置规则。
func (c *RealClient) DeleteMfaExemptionRule(ctx context.Context, cred Credentials, region, domainID, ruleID string) error {
	if strings.HasPrefix(ruleID, "OciConsole") {
		return fmt.Errorf("delete sign-on rule: %s is a built-in rule and cannot be deleted", ruleID)
	}
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return err
	}
	rule, err := dc.GetRule(ctx, identitydomains.GetRuleRequest{RuleId: &ruleID})
	if err != nil {
		return fmt.Errorf("get rule %s: %w", ruleID, err)
	}
	if err := removeRuleFromPolicy(ctx, dc, ruleID); err != nil {
		return err
	}
	if _, err := dc.DeleteRule(ctx, identitydomains.DeleteRuleRequest{RuleId: &ruleID}); err != nil {
		return fmt.Errorf("delete rule %s: %w", ruleID, err)
	}
	cg := rule.ConditionGroup
	if cg != nil && cg.Type == identitydomains.RuleConditionGroupTypeCondition && cg.Value != nil {
		deleteConditionQuiet(ctx, dc, *cg.Value)
	}
	return nil
}

func deleteRuleQuiet(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string) {
	_, _ = dc.DeleteRule(ctx, identitydomains.DeleteRuleRequest{RuleId: &ruleID})
}

func deleteConditionQuiet(ctx context.Context, dc identitydomains.IdentityDomainsClient, condID string) {
	_, _ = dc.DeleteCondition(ctx, identitydomains.DeleteConditionRequest{ConditionId: &condID})
}
