package minio

import (
	"context"
	"net/http"
	"time"

	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

// ConnectionManager manages MinIO connections and operations
type ConnectionManager struct {
	client *minio.Client
	config Config
	logger Logger
}

// NewConnectionManager creates a new MinIO connection manager
func NewConnectionManager(config Config, logger Logger) (*ConnectionManager, error) {
	if err := config.Validate(); err != nil {
		return nil, err
	}

	return &ConnectionManager{
		config: config,
		logger: logger,
	}, nil
}

// Connect establishes connection to MinIO
func (cm *ConnectionManager) Connect() error {
	// Create MinIO client
	minioClient, err := minio.New(cm.config.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(cm.config.AccessKeyID, cm.config.SecretAccessKey, ""),
		Secure: cm.config.UseSSL,
		Region: cm.config.Region,
		Transport: &http.Transport{
			ResponseHeaderTimeout: cm.config.RequestTimeout,
			TLSHandshakeTimeout:   10 * time.Second,
			DisableKeepAlives:     false,
		},
	})
	if err != nil {
		cm.logger.Error("Failed to create MinIO client", map[string]interface{}{
			"endpoint": cm.config.Endpoint,
			"error":    err.Error(),
		})
		return NewErrorWithCause(ErrCodeConnectionFailed, "failed to create MinIO client", err)
	}

	cm.client = minioClient

	// Test connection by checking if we can reach the server
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Try bucket exists check with shorter timeout
	exists, err := cm.client.BucketExists(ctx, cm.config.DefaultBucket)
	if err != nil {
		cm.logger.Error("Failed to connect to MinIO", map[string]interface{}{
			"endpoint": cm.config.Endpoint,
			"bucket":   cm.config.DefaultBucket,
			"error":    err.Error(),
		})
		return NewErrorWithCause(ErrCodeConnectionFailed, "failed to connect to MinIO", err)
	}

	cm.logger.Info("Successfully connected to MinIO", map[string]interface{}{
		"endpoint": cm.config.Endpoint,
		"region":   cm.config.Region,
		"bucket":   cm.config.DefaultBucket,
		"exists":   exists,
	})
	return nil
}

// Disconnect closes the MinIO connection
func (cm *ConnectionManager) Disconnect(ctx context.Context) error {
	if cm.client == nil {
		return nil
	}

	// MinIO client doesn't have a disconnect method, but we can set it to nil
	cm.client = nil

	cm.logger.Info("Disconnected from MinIO", nil)
	return nil
}

// Ping tests the MinIO connection
func (cm *ConnectionManager) Ping(ctx context.Context) error {
	if cm.client == nil {
		return ErrNotConnected
	}

	_, err := cm.client.ListBuckets(ctx)
	if err != nil {
		cm.logger.Error("MinIO ping failed", map[string]interface{}{
			"error": err.Error(),
		})
		return NewErrorWithCause(ErrCodeConnectionFailed, "ping failed", err)
	}

	return nil
}

// IsConnected returns true if connected to MinIO
func (cm *ConnectionManager) IsConnected() bool {
	return cm.client != nil
}

// GetClient returns the underlying MinIO client
func (cm *ConnectionManager) GetClient() interface{} {
	return cm.client
}

// CreateBucket creates a new bucket if it doesn't exist
func (cm *ConnectionManager) CreateBucket(ctx context.Context, bucketName string) error {
	if cm.client == nil {
		return ErrNotConnected
	}

	if bucketName == "" {
		return ErrInvalidBucketName
	}

	exists, err := cm.client.BucketExists(ctx, bucketName)
	if err != nil {
		cm.logger.Error("Failed to check bucket existence", map[string]interface{}{
			"bucket": bucketName,
			"error":  err.Error(),
		})
		return NewErrorWithCause(ErrCodeBucketNotFound, "failed to check bucket existence", err)
	}

	if exists {
		cm.logger.Debug("Bucket already exists", map[string]interface{}{
			"bucket": bucketName,
		})
		return nil
	}

	err = cm.client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{
		Region: cm.config.Region,
	})
	if err != nil {
		cm.logger.Error("Failed to create bucket", map[string]interface{}{
			"bucket": bucketName,
			"error":  err.Error(),
		})
		return NewErrorWithCause("BUCKET_CREATE_FAILED", "failed to create bucket", err)
	}

	cm.logger.Info("Bucket created successfully", map[string]interface{}{
		"bucket": bucketName,
	})

	return nil
}

// DeleteBucket deletes a bucket
func (cm *ConnectionManager) DeleteBucket(ctx context.Context, bucketName string) error {
	if cm.client == nil {
		return ErrNotConnected
	}

	if bucketName == "" {
		return ErrInvalidBucketName
	}

	err := cm.client.RemoveBucket(ctx, bucketName)
	if err != nil {
		cm.logger.Error("Failed to delete bucket", map[string]interface{}{
			"bucket": bucketName,
			"error":  err.Error(),
		})
		return NewErrorWithCause("BUCKET_DELETE_FAILED", "failed to delete bucket", err)
	}

	cm.logger.Info("Bucket deleted successfully", map[string]interface{}{
		"bucket": bucketName,
	})

	return nil
}

// BucketExists checks if a bucket exists
func (cm *ConnectionManager) BucketExists(ctx context.Context, bucketName string) (bool, error) {
	if cm.client == nil {
		return false, ErrNotConnected
	}

	if bucketName == "" {
		return false, ErrInvalidBucketName
	}

	exists, err := cm.client.BucketExists(ctx, bucketName)
	if err != nil {
		cm.logger.Error("Failed to check bucket existence", map[string]interface{}{
			"bucket": bucketName,
			"error":  err.Error(),
		})
		return false, NewErrorWithCause(ErrCodeBucketNotFound, "failed to check bucket existence", err)
	}

	return exists, nil
}

// ListBuckets lists all buckets
func (cm *ConnectionManager) ListBuckets(ctx context.Context) ([]string, error) {
	if cm.client == nil {
		return nil, ErrNotConnected
	}

	buckets, err := cm.client.ListBuckets(ctx)
	if err != nil {
		cm.logger.Error("Failed to list buckets", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, NewErrorWithCause("LIST_BUCKETS_FAILED", "failed to list buckets", err)
	}

	bucketNames := make([]string, len(buckets))
	for i, bucket := range buckets {
		bucketNames[i] = bucket.Name
	}

	cm.logger.Debug("Buckets listed successfully", map[string]interface{}{
		"count": len(bucketNames),
	})

	return bucketNames, nil
}

// GetBucketInfo gets bucket information
func (cm *ConnectionManager) GetBucketInfo(ctx context.Context, bucketName string) (map[string]interface{}, error) {
	if cm.client == nil {
		return nil, ErrNotConnected
	}

	if bucketName == "" {
		return nil, ErrInvalidBucketName
	}

	// For now, return basic bucket info
	// In a real implementation, you might want to get more detailed info
	info := map[string]interface{}{
		"name":   bucketName,
		"region": cm.config.Region,
	}

	cm.logger.Debug("Bucket info retrieved", map[string]interface{}{
		"bucket": bucketName,
	})

	return info, nil
}
