package service

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"go.uber.org/zap"
)

// StorageBackend represents a storage backend for usage data
type StorageBackend interface {
	Load(key string) ([]byte, error)
	Save(key string, data []byte) error
}

// LocalStorageBackend implements StorageBackend for local file system
type LocalStorageBackend struct {
	baseDir string
}

// NewLocalStorageBackend creates a new local storage backend
func NewLocalStorageBackend(baseDir string) *LocalStorageBackend {
	return &LocalStorageBackend{
		baseDir: baseDir,
	}
}

// Load reads data from a local file
func (l *LocalStorageBackend) Load(key string) ([]byte, error) {
	filePath := l.getFilePath(key)
	return os.ReadFile(filePath)
}

// Save writes data to a local file
func (l *LocalStorageBackend) Save(key string, data []byte) error {
	filePath := l.getFilePath(key)

	// Ensure directory exists
	if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Write to temporary file first, then rename (atomic operation)
	tempFile := filePath + ".tmp"
	if err := os.WriteFile(tempFile, data, 0644); err != nil {
		return fmt.Errorf("failed to write temp file: %w", err)
	}

	if err := os.Rename(tempFile, filePath); err != nil {
		os.Remove(tempFile) // Clean up temp file
		return fmt.Errorf("failed to rename temp file: %w", err)
	}

	return nil
}

func (l *LocalStorageBackend) getFilePath(key string) string {
	return filepath.Join(l.baseDir, key)
}

// S3StorageBackend implements StorageBackend for AWS S3
type S3StorageBackend struct {
	client *s3.Client
	bucket string
	prefix string
}

func getS3BucketRegion(bucketName string) (string, error) {
	cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-east-1"))
	if err != nil {
		return "", fmt.Errorf("failed to load SDK configuration: %w", err)
	}
	s3client := s3.NewFromConfig(cfg)
	region, err := manager.GetBucketRegion(context.TODO(), s3client, bucketName)
	if err != nil {
		return "", err
	}
	return region, nil
}

// NewS3StorageBackend creates a new S3 storage backend
func NewS3StorageBackend(s3Path string) (*S3StorageBackend, error) {
	// Parse s3://bucket/prefix format
	if !strings.HasPrefix(s3Path, "s3://") {
		return nil, fmt.Errorf("invalid S3 path format, expected s3://bucket/prefix")
	}

	path := strings.TrimPrefix(s3Path, "s3://")
	if path == "" {
		return nil, fmt.Errorf("invalid S3 path format, bucket name is required")
	}

	parts := strings.SplitN(path, "/", 2)
	bucket := parts[0]
	if bucket == "" {
		return nil, fmt.Errorf("invalid S3 path format, bucket name is required")
	}

	prefix := ""
	if len(parts) > 1 && parts[1] != "" {
		prefix = parts[1]
		if !strings.HasSuffix(prefix, "/") {
			prefix += "/"
		}
	}

	region, err := getS3BucketRegion(bucket)
	if err != nil {
		return nil, fmt.Errorf("failed to resolve region for bucket %s: %w", bucket, err)
	}

	zap.L().Info("Resolved S3 bucket region",
		zap.String("bucketName", bucket),
		zap.String("region", region))

	// Load AWS config
	cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
	if err != nil {
		return nil, fmt.Errorf("failed to load AWS config: %w", err)
	}

	client := s3.NewFromConfig(cfg)

	_, err = client.HeadBucket(context.TODO(), &s3.HeadBucketInput{
		Bucket: aws.String(bucket),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to head bucket %s: %w", bucket, err)
	}

	return &S3StorageBackend{
		client: client,
		bucket: bucket,
		prefix: prefix,
	}, nil
}

// Load reads data from S3
func (s *S3StorageBackend) Load(key string) ([]byte, error) {
	s3Key := s.prefix + key

	result, err := s.client.GetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: aws.String(s.bucket),
		Key:    aws.String(s3Key),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to get object from S3: %w", err)
	}
	defer result.Body.Close()

	data, err := io.ReadAll(result.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read S3 object body: %w", err)
	}

	return data, nil
}

// Save writes data to S3
func (s *S3StorageBackend) Save(key string, data []byte) error {
	s3Key := s.prefix + key

	_, err := s.client.PutObject(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.bucket),
		Key:    aws.String(s3Key),
		Body:   bytes.NewReader(data),
	})
	if err != nil {
		return fmt.Errorf("failed to put object to S3: %w", err)
	}

	zap.L().Debug("Saved data to S3", zap.String("key", s3Key))
	return nil
}
