// Copyright 2026 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package object

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/casdoor/casdoor/util"
)

// AwsIamSyncerProvider implements SyncerProvider for AWS IAM API-based syncers
type AwsIamSyncerProvider struct {
	Syncer    *Syncer
	iamClient *iam.IAM
}

// InitAdapter initializes the AWS IAM syncer
func (p *AwsIamSyncerProvider) InitAdapter() error {
	// syncer.Host should be the AWS region (e.g., "us-east-1")
	// syncer.User should be the AWS Access Key ID
	// syncer.Password should be the AWS Secret Access Key

	region := p.Syncer.Host
	if region == "" {
		return fmt.Errorf("AWS region (host field) is required for AWS IAM syncer")
	}

	accessKeyId := p.Syncer.User
	if accessKeyId == "" {
		return fmt.Errorf("AWS Access Key ID (user field) is required for AWS IAM syncer")
	}

	secretAccessKey := p.Syncer.Password
	if secretAccessKey == "" {
		return fmt.Errorf("AWS Secret Access Key (password field) is required for AWS IAM syncer")
	}

	// Create AWS session
	sess, err := session.NewSession(&aws.Config{
		Region:      aws.String(region),
		Credentials: credentials.NewStaticCredentials(accessKeyId, secretAccessKey, ""),
	})
	if err != nil {
		return fmt.Errorf("failed to create AWS session: %w", err)
	}

	// Create IAM client
	p.iamClient = iam.New(sess)

	return nil
}

// GetOriginalUsers retrieves all users from AWS IAM API
func (p *AwsIamSyncerProvider) GetOriginalUsers() ([]*OriginalUser, error) {
	if p.iamClient == nil {
		if err := p.InitAdapter(); err != nil {
			return nil, err
		}
	}

	return p.getAwsIamUsers()
}

// AddUser adds a new user to AWS IAM (not supported for read-only API)
func (p *AwsIamSyncerProvider) AddUser(user *OriginalUser) (bool, error) {
	// AWS IAM syncer is typically read-only
	return false, fmt.Errorf("adding users to AWS IAM is not supported")
}

// UpdateUser updates an existing user in AWS IAM (not supported for read-only API)
func (p *AwsIamSyncerProvider) UpdateUser(user *OriginalUser) (bool, error) {
	// AWS IAM syncer is typically read-only
	return false, fmt.Errorf("updating users in AWS IAM is not supported")
}

// TestConnection tests the AWS IAM API connection
func (p *AwsIamSyncerProvider) TestConnection() error {
	if p.iamClient == nil {
		if err := p.InitAdapter(); err != nil {
			return err
		}
	}

	// Try to list users with a limit of 1 to test the connection
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	input := &iam.ListUsersInput{
		MaxItems: aws.Int64(1),
	}

	_, err := p.iamClient.ListUsersWithContext(ctx, input)
	return err
}

// Close closes any open connections
func (p *AwsIamSyncerProvider) Close() error {
	// AWS IAM client doesn't require explicit cleanup
	p.iamClient = nil
	return nil
}

// getAwsIamUsers gets all users from AWS IAM API
func (p *AwsIamSyncerProvider) getAwsIamUsers() ([]*OriginalUser, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	allUsers := []*iam.User{}
	var marker *string

	// Paginate through all users
	for {
		input := &iam.ListUsersInput{
			Marker: marker,
		}

		result, err := p.iamClient.ListUsersWithContext(ctx, input)
		if err != nil {
			return nil, fmt.Errorf("failed to list IAM users: %w", err)
		}

		allUsers = append(allUsers, result.Users...)

		if result.IsTruncated == nil || !*result.IsTruncated {
			break
		}

		marker = result.Marker
	}

	// Convert AWS IAM users to Casdoor OriginalUser
	originalUsers := []*OriginalUser{}
	for _, iamUser := range allUsers {
		originalUser, err := p.awsIamUserToOriginalUser(iamUser)
		if err != nil {
			// Log error but continue processing other users
			userName := "unknown"
			if iamUser.UserName != nil {
				userName = *iamUser.UserName
			}
			fmt.Printf("Warning: Failed to convert IAM user %s: %v\n", userName, err)
			continue
		}
		originalUsers = append(originalUsers, originalUser)
	}

	return originalUsers, nil
}

// awsIamUserToOriginalUser converts AWS IAM user to Casdoor OriginalUser
func (p *AwsIamSyncerProvider) awsIamUserToOriginalUser(iamUser *iam.User) (*OriginalUser, error) {
	if iamUser == nil {
		return nil, fmt.Errorf("IAM user is nil")
	}

	user := &OriginalUser{
		Address:    []string{},
		Properties: map[string]string{},
		Groups:     []string{},
	}

	// Set ID from UserId (unique identifier)
	if iamUser.UserId != nil {
		user.Id = *iamUser.UserId
	}

	// Set Name from UserName
	if iamUser.UserName != nil {
		user.Name = *iamUser.UserName
	}

	// Set DisplayName (use UserName if not available separately)
	if iamUser.UserName != nil {
		user.DisplayName = *iamUser.UserName
	}

	// Set CreatedTime from CreateDate
	if iamUser.CreateDate != nil {
		user.CreatedTime = iamUser.CreateDate.Format(time.RFC3339)
	} else {
		user.CreatedTime = util.GetCurrentTime()
	}

	// Get user tags which might contain additional information
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	tagsInput := &iam.ListUserTagsInput{
		UserName: iamUser.UserName,
	}

	tagsResult, err := p.iamClient.ListUserTagsWithContext(ctx, tagsInput)
	if err == nil && tagsResult != nil {
		// Process tags to extract additional user information
		for _, tag := range tagsResult.Tags {
			if tag.Key != nil && tag.Value != nil {
				key := *tag.Key
				value := *tag.Value

				switch key {
				case "Email", "email":
					user.Email = value
				case "Phone", "phone":
					user.Phone = value
				case "DisplayName", "displayName":
					user.DisplayName = value
				case "FirstName", "firstName":
					user.FirstName = value
				case "LastName", "lastName":
					user.LastName = value
				case "Title", "title":
					user.Title = value
				case "Department", "department":
					user.Affiliation = value
				default:
					// Store other tags in Properties
					user.Properties[key] = value
				}
			}
		}
	}

	// AWS IAM users are active by default unless specified in tags
	// Check if there's a "Status" or "Active" tag
	if status, ok := user.Properties["Status"]; ok {
		if status == "Inactive" || status == "Disabled" {
			user.IsForbidden = true
		}
	}
	if active, ok := user.Properties["Active"]; ok {
		if active == "false" || active == "False" || active == "0" {
			user.IsForbidden = true
		}
	}

	return user, nil
}

// GetOriginalGroups retrieves all groups from AWS IAM
func (p *AwsIamSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
	if p.iamClient == nil {
		if err := p.InitAdapter(); err != nil {
			return nil, err
		}
	}

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	allGroups := []*iam.Group{}
	var marker *string

	// Paginate through all groups
	for {
		input := &iam.ListGroupsInput{
			Marker: marker,
		}

		result, err := p.iamClient.ListGroupsWithContext(ctx, input)
		if err != nil {
			return nil, fmt.Errorf("failed to list IAM groups: %w", err)
		}

		allGroups = append(allGroups, result.Groups...)

		if result.IsTruncated == nil || !*result.IsTruncated {
			break
		}

		marker = result.Marker
	}

	// Convert AWS IAM groups to Casdoor OriginalGroup
	originalGroups := []*OriginalGroup{}
	for _, iamGroup := range allGroups {
		if iamGroup.GroupId != nil && iamGroup.GroupName != nil {
			group := &OriginalGroup{
				Id:   *iamGroup.GroupId,
				Name: *iamGroup.GroupName,
			}

			if iamGroup.GroupName != nil {
				group.DisplayName = *iamGroup.GroupName
			}

			originalGroups = append(originalGroups, group)
		}
	}

	return originalGroups, nil
}

// GetOriginalUserGroups retrieves the group IDs that a user belongs to
func (p *AwsIamSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
	if p.iamClient == nil {
		if err := p.InitAdapter(); err != nil {
			return nil, err
		}
	}

	// Note: AWS IAM API requires UserName to query groups, but this interface provides UserId.
	// This is a known limitation. To properly implement this, we would need to:
	// 1. Maintain a mapping cache from UserId to UserName, or
	// 2. Modify the interface to accept both UserId and UserName
	// For now, returning empty groups to maintain interface compatibility.
	// TODO: Implement user group synchronization by maintaining a UserId->UserName mapping

	return []string{}, nil
}
