package object_storage

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

	"github.com/atlasos/calypso/internal/common/logger"
	madmin "github.com/minio/madmin-go/v3"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

// Service handles MinIO object storage operations
type Service struct {
	client      *minio.Client
	adminClient *madmin.AdminClient
	logger      *logger.Logger
	endpoint    string
	accessKey   string
	secretKey   string
}

// NewService creates a new MinIO service
func NewService(endpoint, accessKey, secretKey string, log *logger.Logger) (*Service, error) {
	// Create MinIO client
	minioClient, err := minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKey, secretKey, ""),
		Secure: false, // Set to true if using HTTPS
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create MinIO client: %w", err)
	}

	// Create MinIO Admin client
	adminClient, err := madmin.New(endpoint, accessKey, secretKey, false)
	if err != nil {
		return nil, fmt.Errorf("failed to create MinIO admin client: %w", err)
	}

	return &Service{
		client:      minioClient,
		adminClient: adminClient,
		logger:      log,
		endpoint:    endpoint,
		accessKey:   accessKey,
		secretKey:   secretKey,
	}, nil
}

// Bucket represents a MinIO bucket
type Bucket struct {
	Name         string    `json:"name"`
	CreationDate time.Time `json:"creation_date"`
	Size         int64     `json:"size"`          // Total size in bytes
	Objects      int64     `json:"objects"`       // Number of objects
	AccessPolicy string    `json:"access_policy"` // private, public-read, public-read-write
}

// ListBuckets lists all buckets in MinIO
func (s *Service) ListBuckets(ctx context.Context) ([]*Bucket, error) {
	buckets, err := s.client.ListBuckets(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list buckets: %w", err)
	}

	result := make([]*Bucket, 0, len(buckets))
	for _, bucket := range buckets {
		bucketInfo, err := s.getBucketInfo(ctx, bucket.Name)
		if err != nil {
			s.logger.Warn("Failed to get bucket info", "bucket", bucket.Name, "error", err)
			// Continue with basic info
			result = append(result, &Bucket{
				Name:         bucket.Name,
				CreationDate: bucket.CreationDate,
				Size:         0,
				Objects:      0,
				AccessPolicy: "private",
			})
			continue
		}

		result = append(result, bucketInfo)
	}

	return result, nil
}

// getBucketInfo gets detailed information about a bucket
func (s *Service) getBucketInfo(ctx context.Context, bucketName string) (*Bucket, error) {
	// Get bucket creation date
	buckets, err := s.client.ListBuckets(ctx)
	if err != nil {
		return nil, err
	}

	var creationDate time.Time
	for _, b := range buckets {
		if b.Name == bucketName {
			creationDate = b.CreationDate
			break
		}
	}

	// Get bucket size and object count by listing objects
	var size int64
	var objects int64

	// List objects in bucket to calculate size and count
	objectCh := s.client.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
		Recursive: true,
	})

	for object := range objectCh {
		if object.Err != nil {
			s.logger.Warn("Error listing object", "bucket", bucketName, "error", object.Err)
			continue
		}
		objects++
		size += object.Size
	}

	return &Bucket{
		Name:         bucketName,
		CreationDate: creationDate,
		Size:         size,
		Objects:      objects,
		AccessPolicy: s.getBucketPolicy(ctx, bucketName),
	}, nil
}

// getBucketPolicy gets the access policy for a bucket
func (s *Service) getBucketPolicy(ctx context.Context, bucketName string) string {
	policy, err := s.client.GetBucketPolicy(ctx, bucketName)
	if err != nil {
		return "private"
	}

	// Parse policy JSON to determine access type
	// For simplicity, check if policy allows public read
	if policy != "" {
		// Check if policy contains public read access
		if strings.Contains(policy, "s3:GetObject") && strings.Contains(policy, "Principal") && strings.Contains(policy, "*") {
			if strings.Contains(policy, "s3:PutObject") {
				return "public-read-write"
			}
			return "public-read"
		}
	}

	return "private"
}

// CreateBucket creates a new bucket
func (s *Service) CreateBucket(ctx context.Context, bucketName string) error {
	err := s.client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
	if err != nil {
		return fmt.Errorf("failed to create bucket: %w", err)
	}
	return nil
}

// DeleteBucket deletes a bucket
func (s *Service) DeleteBucket(ctx context.Context, bucketName string) error {
	err := s.client.RemoveBucket(ctx, bucketName)
	if err != nil {
		return fmt.Errorf("failed to delete bucket: %w", err)
	}
	return nil
}

// GetBucketStats gets statistics for a bucket
func (s *Service) GetBucketStats(ctx context.Context, bucketName string) (*Bucket, error) {
	return s.getBucketInfo(ctx, bucketName)
}

// Object represents a MinIO object (file or folder)
type Object struct {
	Name         string    `json:"name"`
	Key          string    `json:"key"`  // Full path/key
	Size         int64     `json:"size"` // Size in bytes (0 for folders)
	LastModified time.Time `json:"last_modified"`
	IsDir        bool      `json:"is_dir"` // True if this is a folder/prefix
	ETag         string    `json:"etag,omitempty"`
}

// ListObjects lists objects in a bucket with optional prefix (folder path)
func (s *Service) ListObjects(ctx context.Context, bucketName, prefix string) ([]*Object, error) {
	// Ensure prefix ends with / if it's not empty and doesn't already end with /
	if prefix != "" && !strings.HasSuffix(prefix, "/") {
		prefix += "/"
	}

	// List objects with prefix
	objectCh := s.client.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
		Prefix:    prefix,
		Recursive: false, // Don't recurse, show folders as separate items
	})

	var objects []*Object
	seenFolders := make(map[string]bool)

	for object := range objectCh {
		if object.Err != nil {
			s.logger.Warn("Error listing object", "bucket", bucketName, "prefix", prefix, "error", object.Err)
			continue
		}

		// Extract folder name from object key
		relativeKey := strings.TrimPrefix(object.Key, prefix)

		// Check if this is a folder (has / in relative path)
		if strings.Contains(relativeKey, "/") {
			// Extract folder name (first part before /)
			folderName := strings.Split(relativeKey, "/")[0]
			folderKey := prefix + folderName + "/"

			// Only add folder once
			if !seenFolders[folderKey] {
				seenFolders[folderKey] = true
				objects = append(objects, &Object{
					Name:         folderName,
					Key:          folderKey,
					Size:         0,
					LastModified: time.Time{},
					IsDir:        true,
				})
			}
		} else {
			// This is a file in the current directory
			objects = append(objects, &Object{
				Name:         relativeKey,
				Key:          object.Key,
				Size:         object.Size,
				LastModified: object.LastModified,
				IsDir:        false,
				ETag:         object.ETag,
			})
		}
	}

	// Sort: folders first, then files, both alphabetically
	sort.Slice(objects, func(i, j int) bool {
		if objects[i].IsDir != objects[j].IsDir {
			return objects[i].IsDir // Folders first
		}
		return objects[i].Name < objects[j].Name
	})

	return objects, nil
}

// User represents a MinIO IAM user
type User struct {
	AccessKey string    `json:"access_key"`
	Status    string    `json:"status"` // "enabled" or "disabled"
	CreatedAt time.Time `json:"created_at"`
}

// ListUsers lists all IAM users in MinIO
func (s *Service) ListUsers(ctx context.Context) ([]*User, error) {
	users, err := s.adminClient.ListUsers(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list users: %w", err)
	}

	result := make([]*User, 0, len(users))
	for accessKey, userInfo := range users {
		status := "enabled"
		if userInfo.Status == madmin.AccountDisabled {
			status = "disabled"
		}

		// MinIO doesn't provide creation date, use current time
		result = append(result, &User{
			AccessKey: accessKey,
			Status:    status,
			CreatedAt: time.Now(),
		})
	}

	return result, nil
}

// CreateUser creates a new IAM user in MinIO
func (s *Service) CreateUser(ctx context.Context, accessKey, secretKey string) error {
	err := s.adminClient.AddUser(ctx, accessKey, secretKey)
	if err != nil {
		return fmt.Errorf("failed to create user: %w", err)
	}
	return nil
}

// DeleteUser deletes an IAM user from MinIO
func (s *Service) DeleteUser(ctx context.Context, accessKey string) error {
	err := s.adminClient.RemoveUser(ctx, accessKey)
	if err != nil {
		return fmt.Errorf("failed to delete user: %w", err)
	}
	return nil
}

// ServiceAccount represents a MinIO service account (access key)
type ServiceAccount struct {
	AccessKey  string    `json:"access_key"`
	SecretKey  string    `json:"secret_key,omitempty"` // Only returned on creation
	ParentUser string    `json:"parent_user"`
	Expiration time.Time `json:"expiration,omitempty"`
	CreatedAt  time.Time `json:"created_at"`
}

// ListServiceAccounts lists all service accounts in MinIO
func (s *Service) ListServiceAccounts(ctx context.Context) ([]*ServiceAccount, error) {
	accounts, err := s.adminClient.ListServiceAccounts(ctx, "")
	if err != nil {
		return nil, fmt.Errorf("failed to list service accounts: %w", err)
	}

	result := make([]*ServiceAccount, 0, len(accounts.Accounts))
	for _, account := range accounts.Accounts {
		var expiration time.Time
		if account.Expiration != nil {
			expiration = *account.Expiration
		}

		result = append(result, &ServiceAccount{
			AccessKey:  account.AccessKey,
			ParentUser: account.ParentUser,
			Expiration: expiration,
			CreatedAt:  time.Now(), // MinIO doesn't provide creation date
		})
	}

	return result, nil
}

// CreateServiceAccount creates a new service account (access key) in MinIO
func (s *Service) CreateServiceAccount(ctx context.Context, parentUser string, policy string, expiration *time.Time) (*ServiceAccount, error) {
	opts := madmin.AddServiceAccountReq{
		TargetUser: parentUser,
	}
	if policy != "" {
		opts.Policy = json.RawMessage(policy)
	}
	if expiration != nil {
		opts.Expiration = expiration
	}

	creds, err := s.adminClient.AddServiceAccount(ctx, opts)
	if err != nil {
		return nil, fmt.Errorf("failed to create service account: %w", err)
	}

	return &ServiceAccount{
		AccessKey:  creds.AccessKey,
		SecretKey:  creds.SecretKey,
		ParentUser: parentUser,
		Expiration: creds.Expiration,
		CreatedAt:  time.Now(),
	}, nil
}

// DeleteServiceAccount deletes a service account from MinIO
func (s *Service) DeleteServiceAccount(ctx context.Context, accessKey string) error {
	err := s.adminClient.DeleteServiceAccount(ctx, accessKey)
	if err != nil {
		return fmt.Errorf("failed to delete service account: %w", err)
	}
	return nil
}
