package oci

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

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

// defaultIdpRuleID 是 Default Identity Provider Policy 的预置规则固定 ID，
// 其 return 中 SamlIDPs 数组决定登录页显示哪些 SAML IdP。
const defaultIdpRuleID = "DefaultIDPRule"

// samlMetadataPath 是 Identity Domain 的 SP SAML 元数据端点。
const samlMetadataPath = "/fed/v1/metadata"

// idpSchema 是 IdentityProvider 资源的 SCIM schema。
const idpSchema = "urn:ietf:params:scim:schemas:oracle:idcs:IdentityProvider"

// IdentityProviderInfo 是一个外部身份提供者的关键字段。
type IdentityProviderInfo struct {
	ID                string     `json:"id"`
	Name              string     `json:"name"`
	Type              string     `json:"type"`
	Enabled           bool       `json:"enabled"`
	PartnerProviderID string     `json:"partnerProviderId"`
	JitEnabled        bool       `json:"jitEnabled"`
	TimeCreated       *time.Time `json:"timeCreated,omitempty"`
}

// CreateIdpInput 是创建 SAML IdP 的输入；零值即控制台默认行为：
// 名称 ID 格式「无」、SAML 断言名称 ID 映射到用户名、JIT 开启建用户不更新、
// 静态分配 Administrators 组（service 层负责把缺省字段填成这些默认值）。
type CreateIdpInput struct {
	Name        string // partnerName，必填
	Metadata    string // IdP metadata XML，必填
	Description string
	IconURL     string // 登录页 logo，仅接受 http(s) 外链 URL（IDCS 拒绝 data URI），空则不设置

	// 映射用户身份
	NameIDFormat       string // 请求的名称 ID 格式，如 saml-none / saml-emailaddress
	AssertionAttribute string // 非空时改用断言属性映射，值为断言中的属性名；空用 NameID
	UserStoreAttribute string // 目标域用户属性：userName 或 emails.primary（主邮箱）

	// JIT 预配
	JitEnabled          bool
	JitCreate           bool // JIT 时创建新用户
	JitUpdate           bool // JIT 时更新既有用户属性
	JitAssignAdminGroup bool // 静态分配 Administrators 组
	JitMapEmail         bool // 属性映射额外映射主邮箱（域要求邮箱必填时需开启）
}

// ListIdentityProviders 实现 Client：列出域内 SAML 身份提供者。
func (c *RealClient) ListIdentityProviders(ctx context.Context, cred Credentials, region, domainID string) ([]IdentityProviderInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return nil, err
	}
	resp, err := dc.ListIdentityProviders(ctx, identitydomains.ListIdentityProvidersRequest{})
	if err != nil {
		return nil, fmt.Errorf("list identity providers: %w", err)
	}
	idps := make([]IdentityProviderInfo, 0, len(resp.Resources))
	for _, idp := range resp.Resources {
		if idp.Type != identitydomains.IdentityProviderTypeSaml {
			continue
		}
		idps = append(idps, toIdpInfo(idp))
	}
	return idps, nil
}

// CreateSamlIdentityProvider 实现 Client：按输入创建禁用态 SAML IdP 并配置 JIT。
func (c *RealClient) CreateSamlIdentityProvider(ctx context.Context, cred Credentials, region, domainID string, in CreateIdpInput) (IdentityProviderInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return IdentityProviderInfo{}, err
	}
	var adminGroup *identitydomains.IdentityProviderJitUserProvAssignedGroups
	if in.JitEnabled && in.JitAssignAdminGroup {
		if adminGroup, err = adminGroupRef(ctx, dc); err != nil {
			return IdentityProviderInfo{}, err
		}
	}
	resp, err := dc.CreateIdentityProvider(ctx, identitydomains.CreateIdentityProviderRequest{
		IdentityProvider: buildSamlIdp(in, adminGroup),
	})
	if err != nil {
		return IdentityProviderInfo{}, fmt.Errorf("create identity provider %s: %w", in.Name, err)
	}
	if in.JitEnabled {
		if err := ensureJitAttributeMappings(ctx, dc, resp.IdentityProvider, jitMappings(in)); err != nil {
			return toIdpInfo(resp.IdentityProvider), err
		}
	}
	return toIdpInfo(resp.IdentityProvider), nil
}

// buildSamlIdp 按输入组装 SAML IdP（创建为禁用态，启用走 activate 接口）。
func buildSamlIdp(in CreateIdpInput, adminGroup *identitydomains.IdentityProviderJitUserProvAssignedGroups) identitydomains.IdentityProvider {
	enabled := false
	boolFalse := false
	idp := identitydomains.IdentityProvider{
		Schemas:                           []string{idpSchema},
		PartnerName:                       &in.Name,
		Enabled:                           &enabled,
		Type:                              identitydomains.IdentityProviderTypeSaml,
		Metadata:                          &in.Metadata,
		NameIdFormat:                      &in.NameIDFormat,
		SignatureHashAlgorithm:            identitydomains.IdentityProviderSignatureHashAlgorithm256,
		IncludeSigningCertInSignature:     &boolFalse,
		UserMappingMethod:                 identitydomains.IdentityProviderUserMappingMethodNameidtouserattribute,
		UserMappingStoreAttribute:         &in.UserStoreAttribute,
		JitUserProvEnabled:                &in.JitEnabled,
		JitUserProvCreateUserEnabled:      &in.JitCreate,
		JitUserProvAttributeUpdateEnabled: &in.JitUpdate,
		JitUserProvGroupAssertionAttributeEnabled: &boolFalse,
		JitUserProvGroupStaticListEnabled:         &in.JitAssignAdminGroup,
		JitUserProvGroupMappingMode:               identitydomains.IdentityProviderJitUserProvGroupMappingModeImplicit,
	}
	if in.AssertionAttribute != "" {
		idp.UserMappingMethod = identitydomains.IdentityProviderUserMappingMethodAssertionattributetouserattribute
		idp.AssertionAttribute = &in.AssertionAttribute
	}
	if in.IconURL != "" {
		idp.IconUrl = &in.IconURL
	}
	if in.Description != "" {
		idp.Description = &in.Description
	}
	if adminGroup != nil {
		idp.JitUserProvAssignedGroups = []identitydomains.IdentityProviderJitUserProvAssignedGroups{*adminGroup}
	}
	return idp
}

// adminGroupRef 查询域内管理员组作为 JIT 静态分配与用户授权目标;
// 按 adminGroupNames 优先序取第一个存在的组,都不存在返回 nil。
func adminGroupRef(ctx context.Context, dc identitydomains.IdentityDomainsClient) (*identitydomains.IdentityProviderJitUserProvAssignedGroups, error) {
	filter := fmt.Sprintf("displayName eq %q or displayName eq %q", adminGroupNames[0], adminGroupNames[1])
	count := len(adminGroupNames)
	resp, err := dc.ListGroups(ctx, identitydomains.ListGroupsRequest{
		Filter:     &filter,
		Attributes: common.String("id,displayName"),
		Count:      &count,
	})
	if err != nil {
		return nil, fmt.Errorf("find administrators group: %w", err)
	}
	for _, name := range adminGroupNames {
		for _, g := range resp.Resources {
			if deref(g.DisplayName) == name && g.Id != nil {
				return &identitydomains.IdentityProviderJitUserProvAssignedGroups{Value: g.Id}, nil
			}
		}
	}
	return nil, nil
}

// jitMappings 生成 JIT 属性映射：来源为 NameID（或指定断言属性），
// 固定映射 用户名 + 姓，按需追加主邮箱（emails[work].value）。
func jitMappings(in CreateIdpInput) []interface{} {
	src := "$(assertion.fed.nameidvalue)"
	if in.AssertionAttribute != "" {
		src = "$(assertion." + in.AssertionAttribute + ")"
	}
	ms := []interface{}{
		map[string]string{"managedObjectAttributeName": src, "idcsAttributeName": "userName"},
		map[string]string{"managedObjectAttributeName": src, "idcsAttributeName": "name.familyName"},
	}
	if in.JitMapEmail {
		ms = append(ms, map[string]string{"managedObjectAttributeName": src, "idcsAttributeName": "emails[work].value"})
	}
	return ms
}

// ensureJitAttributeMappings 把 IdP 自动生成的 JIT 属性映射替换为给定映射。
func ensureJitAttributeMappings(ctx context.Context, dc identitydomains.IdentityDomainsClient, idp identitydomains.IdentityProvider, mappings []interface{}) error {
	ref := idp.JitUserProvAttributes
	if ref == nil || ref.Value == nil {
		got, err := dc.GetIdentityProvider(ctx, identitydomains.GetIdentityProviderRequest{IdentityProviderId: idp.Id})
		if err != nil {
			return fmt.Errorf("reload idp for jit attributes: %w", err)
		}
		ref = got.JitUserProvAttributes
	}
	if ref == nil || ref.Value == nil {
		return fmt.Errorf("idp %s has no jit attribute mapping resource", deref(idp.Id))
	}
	var value interface{} = mappings
	_, err := dc.PatchMappedAttribute(ctx, identitydomains.PatchMappedAttributeRequest{
		MappedAttributeId: ref.Value,
		PatchOp: identitydomains.PatchOp{
			Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
			Operations: []identitydomains.Operations{{
				Op:    identitydomains.OperationsOpReplace,
				Path:  common.String("attributeMappings"),
				Value: &value,
			}},
		},
	})
	if err != nil {
		return fmt.Errorf("patch jit attribute mappings: %w", err)
	}
	return nil
}

// SetIdentityProviderEnabled 实现 Client：启用/停用 IdP 并同步登录页显示。
// 顺序有讲究：启用后才能上登录页，停用前必须先从登录页移除。
func (c *RealClient) SetIdentityProviderEnabled(ctx context.Context, cred Credentials, region, domainID, idpID string, enabled bool) (IdentityProviderInfo, error) {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return IdentityProviderInfo{}, err
	}
	if !enabled {
		if err := updateLoginPageIdps(ctx, dc, idpID, false); err != nil {
			return IdentityProviderInfo{}, err
		}
	}
	idp, err := patchIdpEnabled(ctx, dc, idpID, enabled)
	if err != nil {
		return IdentityProviderInfo{}, err
	}
	if enabled {
		if err := updateLoginPageIdps(ctx, dc, idpID, true); err != nil {
			return toIdpInfo(idp), err
		}
	}
	return toIdpInfo(idp), nil
}

func patchIdpEnabled(ctx context.Context, dc identitydomains.IdentityDomainsClient, idpID string, enabled bool) (identitydomains.IdentityProvider, error) {
	var value interface{} = enabled
	resp, err := dc.PatchIdentityProvider(ctx, identitydomains.PatchIdentityProviderRequest{
		IdentityProviderId: &idpID,
		PatchOp: identitydomains.PatchOp{
			Schemas: []string{scimPatchSchema},
			Operations: []identitydomains.Operations{{
				Op:    identitydomains.OperationsOpReplace,
				Path:  common.String("enabled"),
				Value: &value,
			}},
		},
	})
	if err != nil {
		return identitydomains.IdentityProvider{}, fmt.Errorf("patch idp %s enabled=%v: %w", idpID, enabled, err)
	}
	return resp.IdentityProvider, nil
}

// DeleteIdentityProvider 实现 Client：删除 IdP；先移出登录页并停用。
func (c *RealClient) DeleteIdentityProvider(ctx context.Context, cred Credentials, region, domainID, idpID string) error {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return err
	}
	if err := updateLoginPageIdps(ctx, dc, idpID, false); err != nil {
		return err
	}
	if _, err := patchIdpEnabled(ctx, dc, idpID, false); err != nil {
		return err
	}
	if _, err := dc.DeleteIdentityProvider(ctx, identitydomains.DeleteIdentityProviderRequest{
		IdentityProviderId: &idpID,
	}); err != nil {
		return fmt.Errorf("delete identity provider %s: %w", idpID, err)
	}
	return nil
}

// updateLoginPageIdps 修改预置 DefaultIDPRule 的 SamlIDPs 数组控制登录页显示。
func updateLoginPageIdps(ctx context.Context, dc identitydomains.IdentityDomainsClient, idpID string, show bool) error {
	rule, err := dc.GetRule(ctx, identitydomains.GetRuleRequest{RuleId: common.String(defaultIdpRuleID)})
	if err != nil {
		return fmt.Errorf("get default idp rule: %w", err)
	}
	returns, changed, err := rebuildSamlIdpsReturn(rule.Return, idpID, show)
	if err != nil || !changed {
		return err
	}
	var value interface{} = returns
	_, err = dc.PatchRule(ctx, identitydomains.PatchRuleRequest{
		RuleId: common.String(defaultIdpRuleID),
		PatchOp: identitydomains.PatchOp{
			Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
			Operations: []identitydomains.Operations{{
				Op:    identitydomains.OperationsOpReplace,
				Path:  common.String("return"),
				Value: &value,
			}},
		},
	})
	if err != nil {
		return fmt.Errorf("patch default idp rule: %w", err)
	}
	return nil
}

// rebuildSamlIdpsReturn 重建规则 return 数组，增删 SamlIDPs 中的目标 IdP。
func rebuildSamlIdpsReturn(items []identitydomains.RuleReturn, idpID string, show bool) ([]interface{}, bool, error) {
	returns := make([]interface{}, 0, len(items))
	changed := false
	for _, item := range items {
		name, value := deref(item.Name), deref(item.Value)
		if name == "SamlIDPs" {
			next, ok, err := toggleJSONList(value, idpID, show)
			if err != nil {
				return nil, false, fmt.Errorf("parse SamlIDPs %q: %w", value, err)
			}
			value, changed = next, ok
		}
		returns = append(returns, map[string]string{"name": name, "value": value})
	}
	return returns, changed, nil
}

// toggleJSONList 在 JSON 数组字符串中添加或移除一个元素，返回是否有变化。
func toggleJSONList(list, item string, add bool) (string, bool, error) {
	var items []string
	if list != "" {
		if err := json.Unmarshal([]byte(list), &items); err != nil {
			return "", false, err
		}
	}
	next := make([]string, 0, len(items)+1)
	found := false
	for _, it := range items {
		if it == item {
			found = true
			if !add {
				continue
			}
		}
		next = append(next, it)
	}
	if add && !found {
		next = append(next, item)
	}
	if add == found {
		return list, false, nil
	}
	b, err := json.Marshal(next)
	return string(b), true, err
}

// DownloadDomainSamlMetadata 实现 Client：下载域的 SP SAML 元数据 XML。
// 域设置「访问签名证书」未开启时端点不可用，自动开启后重试
// （联邦配置要求 IdP 侧能读取该元数据，开启公开访问是官方标准做法）。
func (c *RealClient) DownloadDomainSamlMetadata(ctx context.Context, cred Credentials, region, domainID string) ([]byte, error) {
	url, err := c.domainURL(ctx, cred, region, domainID)
	if err != nil {
		return nil, err
	}
	body, status, err := fetchSamlMetadata(ctx, url)
	if err != nil {
		return nil, err
	}
	if status == http.StatusOK {
		return body, nil
	}
	if err := c.enableSigningCertPublicAccess(ctx, cred, region, domainID); err != nil {
		return nil, err
	}
	body, status, err = fetchSamlMetadata(ctx, url)
	if err != nil {
		return nil, err
	}
	if status != http.StatusOK {
		return nil, fmt.Errorf("download saml metadata: HTTP %d: %s", status, truncate(string(body), 200))
	}
	return body, nil
}

// fetchSamlMetadata 匿名请求元数据端点；该端点只支持公开访问，不接受 OCI 签名。
func fetchSamlMetadata(ctx context.Context, url string) ([]byte, int, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url+samlMetadataPath, nil)
	if err != nil {
		return nil, 0, fmt.Errorf("build metadata request: %w", err)
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, 0, fmt.Errorf("download saml metadata: %w", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, 0, fmt.Errorf("read saml metadata: %w", err)
	}
	return body, resp.StatusCode, nil
}

// enableSigningCertPublicAccess 开启域设置「访问签名证书」公开访问。
func (c *RealClient) enableSigningCertPublicAccess(ctx context.Context, cred Credentials, region, domainID string) error {
	dc, err := c.domainsClient(ctx, cred, region, domainID)
	if err != nil {
		return err
	}
	var value interface{} = true
	_, err = dc.PatchSetting(ctx, identitydomains.PatchSettingRequest{
		SettingId: common.String("Settings"),
		PatchOp: identitydomains.PatchOp{
			Schemas: []string{scimPatchSchema},
			Operations: []identitydomains.Operations{{
				Op:    identitydomains.OperationsOpReplace,
				Path:  common.String("signingCertPublicAccess"),
				Value: &value,
			}},
		},
	})
	if err != nil {
		return fmt.Errorf("enable signing cert public access: %w", err)
	}
	return nil
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "..."
}

func toIdpInfo(idp identitydomains.IdentityProvider) IdentityProviderInfo {
	info := IdentityProviderInfo{
		ID:                deref(idp.Id),
		Name:              deref(idp.PartnerName),
		Type:              string(idp.Type),
		Enabled:           idp.Enabled != nil && *idp.Enabled,
		PartnerProviderID: deref(idp.PartnerProviderId),
		JitEnabled:        idp.JitUserProvEnabled != nil && *idp.JitUserProvEnabled,
	}
	if idp.Meta != nil && idp.Meta.Created != nil {
		if t, err := time.Parse(time.RFC3339, *idp.Meta.Created); err == nil {
			info.TimeCreated = &t
		}
	}
	return info
}
