// Copyright 2025 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"fmt"
)

// SCIMSchemasURINamespacesGroups is the SCIM schema URI namespace for group resources.
// This constant represents the standard SCIM core schema for group objects as defined by RFC 7643.
const SCIMSchemasURINamespacesGroups = "urn:ietf:params:scim:schemas:core:2.0:Group"

// SCIMSchemasURINamespacesUser is the SCIM schema URI namespace for user resources.
// This constant represents the standard SCIM core schema for user objects as defined by RFC 7643.
const SCIMSchemasURINamespacesUser = "urn:ietf:params:scim:schemas:core:2.0:User"

// SCIMSchemasURINamespacesListResponse is the SCIM schema URI namespace for list response resources.
// This constant represents the standard SCIM namespace for list responses used in paginated queries, as defined by RFC 7644.
const SCIMSchemasURINamespacesListResponse = "urn:ietf:params:scim:api:messages:2.0:ListResponse"

// SCIMSchemasURINamespacesPatchOp is the SCIM schema URI namespace for patch operations.
// This constant represents the standard SCIM namespace for patch operations as defined by RFC 7644.
const SCIMSchemasURINamespacesPatchOp = "urn:ietf:params:scim:api:messages:2.0:PatchOp"

// SCIMEnterpriseGroupAttributes represents supported SCIM Enterprise group attributes, and represents the result of calling UpdateSCIMGroupAttribute.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#supported-scim-group-attributes
type SCIMEnterpriseGroupAttributes struct {
	DisplayName *string                           `json:"displayName,omitempty"` // Human-readable name for a group.
	Members     []*SCIMEnterpriseDisplayReference `json:"members,omitempty"`     // List of members who are assigned to the group in SCIM provider
	ExternalID  *string                           `json:"externalId,omitempty"`  // This identifier is generated by a SCIM provider. Must be unique per group.
	Schemas     []string                          `json:"schemas,omitempty"`     // The URIs that are used to indicate the namespaces of the SCIM schemas.
	// Bellow: Only populated as a result of calling UpdateSCIMGroupAttribute:
	ID   *string             `json:"id,omitempty"`   // The internally generated id for the group object.
	Meta *SCIMEnterpriseMeta `json:"meta,omitempty"` // The metadata associated with the creation/updates to the group.
}

// SCIMEnterpriseDisplayReference represents a JSON SCIM (System for Cross-domain Identity Management) resource reference.
type SCIMEnterpriseDisplayReference struct {
	Value   string  `json:"value"`             // The local unique identifier for the member (e.g., user ID or group ID).
	Ref     *string `json:"$ref,omitempty"`    // The URI reference to the Members or Groups resource (e.g., /scim/v2/enterprises/{enterprise}/Users/{scim_user_id}).
	Display *string `json:"display,omitempty"` // The display name associated with the member (e.g., user name or group name).
}

// SCIMEnterpriseMeta represents metadata about the SCIM resource.
type SCIMEnterpriseMeta struct {
	ResourceType string     `json:"resourceType"`           // A type of a resource (`User` or `Group`).
	Created      *Timestamp `json:"created,omitempty"`      // A date and time when the user was created.
	LastModified *Timestamp `json:"lastModified,omitempty"` // A date and time when the user was last modified.
	Location     *string    `json:"location,omitempty"`     // A URL location of an object
}

// SCIMEnterpriseGroups represents the result of calling ListProvisionedSCIMGroups.
type SCIMEnterpriseGroups struct {
	Schemas      []string                         `json:"schemas,omitempty"`
	TotalResults *int                             `json:"totalResults,omitempty"`
	Resources    []*SCIMEnterpriseGroupAttributes `json:"Resources,omitempty"`
	StartIndex   *int                             `json:"startIndex,omitempty"`
	ItemsPerPage *int                             `json:"itemsPerPage,omitempty"`
}

// ListProvisionedSCIMGroupsEnterpriseOptions represents query parameters for ListProvisionedSCIMGroups.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#list-provisioned-scim-groups-for-an-enterprise--parameters
type ListProvisionedSCIMGroupsEnterpriseOptions struct {
	// If specified, only results that match the specified filter will be returned.
	// Possible filters are `externalId`, `id`, and `displayName`. For example, `externalId eq "a123"`.
	Filter *string `url:"filter,omitempty"`
	// Excludes the specified attributes from being returned in the results.
	ExcludedAttributes *string `url:"excludedAttributes,omitempty"`
	// Used for pagination: the starting index of the first result to return when paginating through values.
	// Default: 1.
	StartIndex *int `url:"startIndex,omitempty"`
	// Used for pagination: the number of results to return per page.
	// Default: 30.
	Count *int `url:"count,omitempty"`
}

// GetProvisionedSCIMGroupEnterpriseOptions represents query parameters for GetProvisionedSCIMGroup.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#get-scim-provisioning-information-for-an-enterprise-group
type GetProvisionedSCIMGroupEnterpriseOptions struct {
	// Excludes the specified attributes from being returned in the results.
	ExcludedAttributes *string `url:"excludedAttributes,omitempty"`
}

// SCIMEnterpriseUserAttributes represents supported SCIM enterprise user attributes, and represents the result of calling UpdateSCIMUserAttribute.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#supported-scim-user-attributes
type SCIMEnterpriseUserAttributes struct {
	DisplayName string                     `json:"displayName"`     // Human-readable name for a user
	Name        *SCIMEnterpriseUserName    `json:"name,omitempty"`  // The user's full name
	UserName    string                     `json:"userName"`        // The username for the user (GitHub Account after normalized), generated by the SCIM provider. Must be unique per user.
	Emails      []*SCIMEnterpriseUserEmail `json:"emails"`          // List of the user's emails. They all must be unique per user.
	Roles       []*SCIMEnterpriseUserRole  `json:"roles,omitempty"` // List of the user's roles.
	ExternalID  string                     `json:"externalId"`      // This identifier is generated by a SCIM provider. Must be unique per user.
	Active      bool                       `json:"active"`          // Indicates whether the identity is active (true) or should be suspended (false).
	Schemas     []string                   `json:"schemas"`         // The URIs that are used to indicate the namespaces of the SCIM schemas.
	// Bellow: Only populated as a result of calling UpdateSCIMUserAttribute:
	ID     *string                           `json:"id,omitempty"`     // Identifier generated by the GitHub's SCIM endpoint.
	Groups []*SCIMEnterpriseDisplayReference `json:"groups,omitempty"` // List of groups who are assigned to the user in SCIM provider
	Meta   *SCIMEnterpriseMeta               `json:"meta,omitempty"`   // The metadata associated with the creation/updates to the user.
}

// SCIMEnterpriseUserName represents SCIM enterprise user's name information.
type SCIMEnterpriseUserName struct {
	GivenName  string  `json:"givenName"`            // The first name of the user.
	FamilyName string  `json:"familyName"`           // The last name of the user.
	Formatted  *string `json:"formatted,omitempty"`  // The user's full name, including all middle names, titles, and suffixes, formatted for display.
	MiddleName *string `json:"middleName,omitempty"` // The middle name(s) of the user.
}

// SCIMEnterpriseUserEmail represents SCIM enterprise user's emails.
type SCIMEnterpriseUserEmail struct {
	Value   string `json:"value"`   // The email address.
	Primary bool   `json:"primary"` // Whether this email address is the primary address.
	Type    string `json:"type"`    // The type of email address
}

// SCIMEnterpriseUserRole is an enterprise-wide role granted to the user.
type SCIMEnterpriseUserRole struct {
	Value   string  `json:"value"` // The role value representing a user role in GitHub.
	Display *string `json:"display,omitempty"`
	Type    *string `json:"type,omitempty"`
	Primary *bool   `json:"primary,omitempty"` // Is the role a primary role for the user?
}

// SCIMEnterpriseUsers represents the result of calling ListProvisionedSCIMUsers.
type SCIMEnterpriseUsers struct {
	Schemas      []string                        `json:"schemas,omitempty"`
	TotalResults *int                            `json:"totalResults,omitempty"`
	ItemsPerPage *int                            `json:"itemsPerPage,omitempty"`
	StartIndex   *int                            `json:"startIndex,omitempty"`
	Resources    []*SCIMEnterpriseUserAttributes `json:"Resources,omitempty"`
}

// ListProvisionedSCIMUsersEnterpriseOptions represents query parameters for ListProvisionedSCIMUsers.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#list-scim-provisioned-identities-for-an-enterprise
type ListProvisionedSCIMUsersEnterpriseOptions struct {
	// If specified, only results that match the specified filter will be returned.
	// Possible filters are `userName`, `externalId`, `id`, and `displayName`. For example, `externalId eq "a123"`.
	Filter *string `url:"filter,omitempty"`
	// Used for pagination: the starting index of the first result to return when paginating through values.
	// Default: 1.
	StartIndex *int `url:"startIndex,omitempty"`
	// Used for pagination: the number of results to return per page.
	// Default: 30.
	Count *int `url:"count,omitempty"`
}

// SCIMEnterpriseAttribute represents attribute operations for UpdateSCIMGroupAttribute or UpdateSCIMUserAttribute.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#update-an-attribute-for-a-scim-enterprise-group
type SCIMEnterpriseAttribute struct {
	Schemas    []string                            `json:"schemas"`    // The URIs that are used to indicate the namespaces for a SCIM patch operation.
	Operations []*SCIMEnterpriseAttributeOperation `json:"Operations"` // Set of operations to be performed.
}

// SCIMEnterpriseAttributeOperation represents an operation for UpdateSCIMGroupAttribute or UpdateSCIMUserAttribute.
type SCIMEnterpriseAttributeOperation struct {
	Op    string  `json:"op"`              // Can be one of: `add`, `replace`, `remove`.
	Path  *string `json:"path,omitempty"`  // Path to the attribute being modified (Filters are not supported).
	Value any     `json:"value,omitempty"` // New value for the attribute being modified.
}

// ListProvisionedSCIMGroups lists provisioned SCIM groups in an enterprise.
//
// You can improve query search time by using the `excludedAttributes` and
// exclude the specified attributes, e.g. `members` to exclude members from the
// response.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#list-provisioned-scim-groups-for-an-enterprise
//
//meta:operation GET /scim/v2/enterprises/{enterprise}/Groups
func (s *EnterpriseService) ListProvisionedSCIMGroups(ctx context.Context, enterprise string, opts *ListProvisionedSCIMGroupsEnterpriseOptions) (*SCIMEnterpriseGroups, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups", enterprise)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	groups := new(SCIMEnterpriseGroups)
	resp, err := s.client.Do(ctx, req, groups)
	if err != nil {
		return nil, resp, err
	}

	return groups, resp, nil
}

// ListProvisionedSCIMUsers lists provisioned SCIM enterprise users.
//
// When members are part of the group provisioning payload, they're designated
// as external group members. Providers are responsible for maintaining a
// mapping between the `externalId` and `id` for each user.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#list-scim-provisioned-identities-for-an-enterprise
//
//meta:operation GET /scim/v2/enterprises/{enterprise}/Users
func (s *EnterpriseService) ListProvisionedSCIMUsers(ctx context.Context, enterprise string, opts *ListProvisionedSCIMUsersEnterpriseOptions) (*SCIMEnterpriseUsers, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Users", enterprise)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	users := new(SCIMEnterpriseUsers)
	resp, err := s.client.Do(ctx, req, users)
	if err != nil {
		return nil, resp, err
	}

	return users, resp, nil
}

// SetProvisionedSCIMGroup replaces an existing provisioned group’s information.
//
// You must provide all the information required for the group as if you were provisioning it for the first time. Any
// existing group information that you don't provide will be removed, including group membership. To update only
// specific attributes, refer to the `Enterprise.UpdateSCIMGroupAttribute()` method.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#set-scim-information-for-a-provisioned-enterprise-group
//
//meta:operation PUT /scim/v2/enterprises/{enterprise}/Groups/{scim_group_id}
func (s *EnterpriseService) SetProvisionedSCIMGroup(ctx context.Context, enterprise, scimGroupID string, group SCIMEnterpriseGroupAttributes) (*SCIMEnterpriseGroupAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups/%v", enterprise, scimGroupID)
	req, err := s.client.NewRequest("PUT", u, group)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	groupNew := new(SCIMEnterpriseGroupAttributes)
	resp, err := s.client.Do(ctx, req, groupNew)
	if err != nil {
		return nil, resp, err
	}

	return groupNew, resp, nil
}

// SetProvisionedSCIMUser replaces an existing provisioned user's information.
//
// You must supply complete user information, just as you would when provisioning them initially. Any previously
// existing data not provided will be deleted. To update specific attributes only, refer to the
// `Enterprise.UpdateSCIMUserAttribute()` method.
//
// **Warning**: Setting `active: false` will suspend a user, and their handle and email will be obfuscated.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#set-scim-information-for-a-provisioned-enterprise-user
//
//meta:operation PUT /scim/v2/enterprises/{enterprise}/Users/{scim_user_id}
func (s *EnterpriseService) SetProvisionedSCIMUser(ctx context.Context, enterprise, scimUserID string, user SCIMEnterpriseUserAttributes) (*SCIMEnterpriseUserAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Users/%v", enterprise, scimUserID)
	req, err := s.client.NewRequest("PUT", u, user)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	userNew := new(SCIMEnterpriseUserAttributes)
	resp, err := s.client.Do(ctx, req, userNew)
	if err != nil {
		return nil, resp, err
	}

	return userNew, resp, nil
}

// UpdateSCIMGroupAttribute updates a provisioned group’s individual attributes.
//
// The `attribute` parameter must include at least one of the following
// Operations: `add`, `remove`, or `replace`.
//
// The update function can also be used to add group memberships.
//
// You can submit group memberships individually or in batches for improved
// efficiency.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#update-an-attribute-for-a-scim-enterprise-group
//
//meta:operation PATCH /scim/v2/enterprises/{enterprise}/Groups/{scim_group_id}
func (s *EnterpriseService) UpdateSCIMGroupAttribute(ctx context.Context, enterprise, scimGroupID string, attribute SCIMEnterpriseAttribute) (*SCIMEnterpriseGroupAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups/%v", enterprise, scimGroupID)
	req, err := s.client.NewRequest("PATCH", u, attribute)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	group := new(SCIMEnterpriseGroupAttributes)
	resp, err := s.client.Do(ctx, req, group)
	if err != nil {
		return nil, resp, err
	}

	return group, resp, nil
}

// UpdateSCIMUserAttribute updates a provisioned user's individual attributes.
//
// The `attribute` parameter must include at least one of the following
// Operations: `add`, `remove`, or `replace`.
//
// Note: Complex SCIM path selectors that include filters are not supported.
// For example, a path selector defined as `"path": "emails[type eq \"work\"]"`
// will be ineffective.
//
// Warning: Setting `active: false` will suspend a user, and their handle and
// email will be obfuscated.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#update-an-attribute-for-a-scim-enterprise-user
//
//meta:operation PATCH /scim/v2/enterprises/{enterprise}/Users/{scim_user_id}
func (s *EnterpriseService) UpdateSCIMUserAttribute(ctx context.Context, enterprise, scimUserID string, attribute SCIMEnterpriseAttribute) (*SCIMEnterpriseUserAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Users/%v", enterprise, scimUserID)
	req, err := s.client.NewRequest("PATCH", u, attribute)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	user := new(SCIMEnterpriseUserAttributes)
	resp, err := s.client.Do(ctx, req, user)
	if err != nil {
		return nil, resp, err
	}

	return user, resp, nil
}

// ProvisionSCIMGroup creates a SCIM group for an enterprise.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#provision-a-scim-enterprise-group
//
//meta:operation POST /scim/v2/enterprises/{enterprise}/Groups
func (s *EnterpriseService) ProvisionSCIMGroup(ctx context.Context, enterprise string, group SCIMEnterpriseGroupAttributes) (*SCIMEnterpriseGroupAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups", enterprise)
	req, err := s.client.NewRequest("POST", u, group)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	groupProvisioned := new(SCIMEnterpriseGroupAttributes)
	resp, err := s.client.Do(ctx, req, groupProvisioned)
	if err != nil {
		return nil, resp, err
	}

	return groupProvisioned, resp, nil
}

// ProvisionSCIMUser creates an external identity for a new SCIM enterprise user.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#provision-a-scim-enterprise-user
//
//meta:operation POST /scim/v2/enterprises/{enterprise}/Users
func (s *EnterpriseService) ProvisionSCIMUser(ctx context.Context, enterprise string, user SCIMEnterpriseUserAttributes) (*SCIMEnterpriseUserAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Users", enterprise)
	req, err := s.client.NewRequest("POST", u, user)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	userProvisioned := new(SCIMEnterpriseUserAttributes)
	resp, err := s.client.Do(ctx, req, userProvisioned)
	if err != nil {
		return nil, resp, err
	}

	return userProvisioned, resp, nil
}

// GetProvisionedSCIMGroup gets information about a SCIM group.
//
// You can use the `excludedAttributes` from `opts` and exclude the specified
// attributes from being returned in the results. Using this parameter can
// speed up response time.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#get-scim-provisioning-information-for-an-enterprise-group
//
//meta:operation GET /scim/v2/enterprises/{enterprise}/Groups/{scim_group_id}
func (s *EnterpriseService) GetProvisionedSCIMGroup(ctx context.Context, enterprise, scimGroupID string, opts *GetProvisionedSCIMGroupEnterpriseOptions) (*SCIMEnterpriseGroupAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups/%v", enterprise, scimGroupID)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	group := new(SCIMEnterpriseGroupAttributes)
	resp, err := s.client.Do(ctx, req, group)
	if err != nil {
		return nil, resp, err
	}

	return group, resp, nil
}

// GetProvisionedSCIMUser gets information about a SCIM user.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#get-scim-provisioning-information-for-an-enterprise-user
//
//meta:operation GET /scim/v2/enterprises/{enterprise}/Users/{scim_user_id}
func (s *EnterpriseService) GetProvisionedSCIMUser(ctx context.Context, enterprise, scimUserID string) (*SCIMEnterpriseUserAttributes, *Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Users/%v", enterprise, scimUserID)

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("Accept", mediaTypeSCIM)

	user := new(SCIMEnterpriseUserAttributes)
	resp, err := s.client.Do(ctx, req, user)
	if err != nil {
		return nil, resp, err
	}

	return user, resp, nil
}

// DeleteSCIMGroup deletes a SCIM group from an enterprise.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#delete-a-scim-group-from-an-enterprise
//
//meta:operation DELETE /scim/v2/enterprises/{enterprise}/Groups/{scim_group_id}
func (s *EnterpriseService) DeleteSCIMGroup(ctx context.Context, enterprise, scimGroupID string) (*Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups/%v", enterprise, scimGroupID)
	req, err := s.client.NewRequest("DELETE", u, nil)
	if err != nil {
		return nil, err
	}

	return s.client.Do(ctx, req, nil)
}

// DeleteSCIMUser deletes a SCIM user from an enterprise.
//
// Suspends a SCIM user permanently from an enterprise. This action will:
// remove all the user's data, anonymize their login, email, and display name,
// erase all external identity SCIM attributes, delete the user's emails,
// avatar, PATs, SSH keys, OAuth authorizations, GPG keys, and SAML mappings.
// This action is irreversible.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#delete-a-scim-user-from-an-enterprise
//
//meta:operation DELETE /scim/v2/enterprises/{enterprise}/Users/{scim_user_id}
func (s *EnterpriseService) DeleteSCIMUser(ctx context.Context, enterprise, scimUserID string) (*Response, error) {
	u := fmt.Sprintf("scim/v2/enterprises/%v/Users/%v", enterprise, scimUserID)
	req, err := s.client.NewRequest("DELETE", u, nil)
	if err != nil {
		return nil, err
	}

	return s.client.Do(ctx, req, nil)
}
