package services

import (
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)

// OSSObject representa un objeto/archivo listado en el bucket
type OSSObject struct {
	Key          string    `json:"key"`
	Size         int64     `json:"size"`
	LastModified time.Time `json:"last_modified"`
	ETag         string    `json:"etag"`
}

// OSSListResult resultado paginado de listado de objetos
type OSSListResult struct {
	Objects        []OSSObject `json:"objects"`
	CommonPrefixes []string    `json:"prefixes"`
	IsTruncated    bool        `json:"is_truncated"`
	NextMarker     string      `json:"next_marker"`
}

// OSSProvider interface común para Alibaba OSS y S3-compatible (MinIO)
type OSSProvider interface {
	UploadFile(objectKey, filePath string) error
	DeleteFile(objectKey string) error
	ListObjects(prefix, marker string, maxKeys int) (*OSSListResult, error)
	SignedURL(objectKey string, expireSeconds int) (string, error)
	UploadFromReader(objectKey, contentType string, r io.Reader) error
	DeleteObjects(keys []string) error
	BucketName() string
	Endpoint() string
	PublicURL(objectKey string) string
}

// ── Alibaba OSS ──────────────────────────────────────────────────────────────

type alibabaOSS struct {
	client     *oss.Client
	bucket     *oss.Bucket
	bucketName string
	endpoint   string
}

func newAlibabaOSS(cfg *models.OssApi) (OSSProvider, error) {
	client, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret)
	if err != nil {
		return nil, fmt.Errorf("error creando cliente Alibaba OSS: %w", err)
	}
	bucket, err := client.Bucket(cfg.BucketName)
	if err != nil {
		return nil, fmt.Errorf("error obteniendo bucket Alibaba: %w", err)
	}
	return &alibabaOSS{
		client:     client,
		bucket:     bucket,
		bucketName: cfg.BucketName,
		endpoint:   cfg.Endpoint,
	}, nil
}

func (s *alibabaOSS) BucketName() string { return s.bucketName }
func (s *alibabaOSS) Endpoint() string   { return s.endpoint }
func (s *alibabaOSS) PublicURL(objectKey string) string {
	return fmt.Sprintf("https://%s.%s/%s", s.bucketName, s.endpoint, objectKey)
}

func (s *alibabaOSS) UploadFile(objectKey, filePath string) error {
	if err := s.bucket.PutObjectFromFile(objectKey, filePath); err != nil {
		return fmt.Errorf("error subiendo archivo a Alibaba OSS: %w", err)
	}
	log.Printf("Archivo '%s' subido como '%s' (Alibaba)", filePath, objectKey)
	return nil
}

func (s *alibabaOSS) DeleteFile(objectKey string) error {
	if err := s.bucket.DeleteObject(objectKey); err != nil {
		return fmt.Errorf("error eliminando archivo de Alibaba OSS: %w", err)
	}
	log.Printf("Archivo eliminado de Alibaba OSS: %s", objectKey)
	return nil
}

func (s *alibabaOSS) ListObjects(prefix, marker string, maxKeys int) (*OSSListResult, error) {
	if maxKeys <= 0 || maxKeys > 1000 {
		maxKeys = 100
	}
	opts := []oss.Option{
		oss.MaxKeys(maxKeys),
		oss.Delimiter("/"),
	}
	if prefix != "" {
		opts = append(opts, oss.Prefix(prefix))
	}
	if marker != "" {
		opts = append(opts, oss.Marker(marker))
	}
	resp, err := s.bucket.ListObjects(opts...)
	if err != nil {
		return nil, fmt.Errorf("error listando objetos Alibaba OSS: %w", err)
	}
	result := &OSSListResult{
		IsTruncated: resp.IsTruncated,
		NextMarker:  resp.NextMarker,
	}
	for _, obj := range resp.Objects {
		result.Objects = append(result.Objects, OSSObject{
			Key:          obj.Key,
			Size:         obj.Size,
			LastModified: obj.LastModified,
			ETag:         obj.ETag,
		})
	}
	for _, cp := range resp.CommonPrefixes {
		result.CommonPrefixes = append(result.CommonPrefixes, cp)
	}
	return result, nil
}

func (s *alibabaOSS) SignedURL(objectKey string, expireSeconds int) (string, error) {
	if expireSeconds <= 0 {
		expireSeconds = 3600
	}
	url, err := s.bucket.SignURL(objectKey, oss.HTTPGet, int64(expireSeconds))
	if err != nil {
		return "", fmt.Errorf("error generando URL firmada Alibaba: %w", err)
	}
	return url, nil
}

func (s *alibabaOSS) UploadFromReader(objectKey, contentType string, r io.Reader) error {
	var opts []oss.Option
	if contentType != "" {
		opts = append(opts, oss.ContentType(contentType))
	}
	if err := s.bucket.PutObject(objectKey, r, opts...); err != nil {
		return fmt.Errorf("error subiendo objeto a Alibaba OSS: %w", err)
	}
	log.Printf("Objeto subido a Alibaba OSS: %s", objectKey)
	return nil
}

func (s *alibabaOSS) DeleteObjects(keys []string) error {
	_, err := s.bucket.DeleteObjects(keys)
	if err != nil {
		return fmt.Errorf("error eliminando objetos en batch de Alibaba OSS: %w", err)
	}
	return nil
}

// ── S3-compatible (MinIO) ────────────────────────────────────────────────────

type s3OSS struct {
	client     *minio.Client
	bucketName string
	endpoint   string
	publicURL  string
}

func newS3OSS(cfg *models.OssApi) (OSSProvider, error) {
	useSSL := false
	endpoint := cfg.Endpoint
	// Detectar si el endpoint usa HTTPS
	if len(endpoint) > 8 && endpoint[:8] == "https://" {
		useSSL = true
		endpoint = endpoint[8:]
	} else if len(endpoint) > 7 && endpoint[:7] == "http://" {
		endpoint = endpoint[7:]
	}

	client, err := minio.New(endpoint, &minio.Options{
		Creds:        credentials.NewStaticV4(cfg.AccessKeyID, cfg.AccessKeySecret, ""),
		Secure:       useSSL,
		Region:       cfg.Region,
		BucketLookup: minio.BucketLookupPath,
	})
	if err != nil {
		return nil, fmt.Errorf("error creando cliente S3/MinIO: %w", err)
	}

	return &s3OSS{
		client:     client,
		bucketName: cfg.BucketName,
		endpoint:   cfg.Endpoint,
		publicURL:  cfg.PublicURL,
	}, nil
}

func (s *s3OSS) BucketName() string { return s.bucketName }
func (s *s3OSS) Endpoint() string   { return s.endpoint }
func (s *s3OSS) PublicURL(objectKey string) string {
	if s.publicURL != "" {
		return fmt.Sprintf("%s/%s/%s", s.publicURL, s.bucketName, objectKey)
	}
	return fmt.Sprintf("%s/%s/%s", s.endpoint, s.bucketName, objectKey)
}

func (s *s3OSS) UploadFile(objectKey, filePath string) error {
	ctx := context.Background()
	_, err := s.client.FPutObject(ctx, s.bucketName, objectKey, filePath, minio.PutObjectOptions{
		ContentDisposition: "attachment",
	})
	if err != nil {
		return fmt.Errorf("error subiendo archivo a S3: %w", err)
	}
	log.Printf("Archivo '%s' subido como '%s' (S3)", filePath, objectKey)
	return nil
}

func (s *s3OSS) DeleteFile(objectKey string) error {
	ctx := context.Background()
	if err := s.client.RemoveObject(ctx, s.bucketName, objectKey, minio.RemoveObjectOptions{}); err != nil {
		return fmt.Errorf("error eliminando archivo de S3: %w", err)
	}
	log.Printf("Archivo eliminado de S3: %s", objectKey)
	return nil
}

func (s *s3OSS) ListObjects(prefix, marker string, maxKeys int) (*OSSListResult, error) {
	ctx := context.Background()
	if maxKeys <= 0 || maxKeys > 1000 {
		maxKeys = 100
	}

	opts := minio.ListObjectsOptions{
		Prefix:    prefix,
		Recursive: false, // false = usa '/' como delimiter
		MaxKeys:   maxKeys,
	}

	if marker != "" {
		opts.StartAfter = marker
	}

	result := &OSSListResult{}
	for obj := range s.client.ListObjects(ctx, s.bucketName, opts) {
		if obj.Err != nil {
			return nil, fmt.Errorf("error listando objetos S3: %w", obj.Err)
		}
		if len(obj.Key) > 0 && obj.Key[len(obj.Key)-1] == '/' {
			result.CommonPrefixes = append(result.CommonPrefixes, obj.Key)
		} else {
			result.Objects = append(result.Objects, OSSObject{
				Key:          obj.Key,
				Size:         obj.Size,
				LastModified: obj.LastModified,
				ETag:         obj.ETag,
			})
		}
	}

	// Determinar si hay más resultados y generar next_marker
	if len(result.Objects) > 0 {
		last := result.Objects[len(result.Objects)-1]
		result.NextMarker = last.Key
		result.IsTruncated = len(result.Objects) >= maxKeys
	}

	return result, nil
}

func (s *s3OSS) SignedURL(objectKey string, expireSeconds int) (string, error) {
	if expireSeconds <= 0 {
		expireSeconds = 3600
	}
	ctx := context.Background()
	url, err := s.client.PresignedGetObject(ctx, s.bucketName, objectKey, time.Duration(expireSeconds)*time.Second, nil)
	if err != nil {
		return "", fmt.Errorf("error generando URL firmada S3: %w", err)
	}
	return url.String(), nil
}

func (s *s3OSS) UploadFromReader(objectKey, contentType string, r io.Reader) error {
	ctx := context.Background()
	opts := minio.PutObjectOptions{
		ContentDisposition: "attachment",
	}
	if contentType != "" {
		opts.ContentType = contentType
	}
	_, err := s.client.PutObject(ctx, s.bucketName, objectKey, r, -1, opts)
	if err != nil {
		return fmt.Errorf("error subiendo objeto a S3: %w", err)
	}
	log.Printf("Objeto subido a S3: %s", objectKey)
	return nil
}

func (s *s3OSS) DeleteObjects(keys []string) error {
	ctx := context.Background()
	opts := minio.RemoveObjectsOptions{}
	ch := make(chan minio.ObjectInfo, len(keys))
	go func() {
		defer close(ch)
		for _, k := range keys {
			ch <- minio.ObjectInfo{Key: k}
		}
	}()
	for err := range s.client.RemoveObjects(ctx, s.bucketName, ch, opts) {
		if err.Err != nil {
			return fmt.Errorf("error eliminando objetos en batch de S3: %w", err.Err)
		}
	}
	return nil
}

// ── Factory ──────────────────────────────────────────────────────────────────

// NewOSSProvider crea el provider correcto según la configuración
func NewOSSProvider(cfg *models.OssApi) (OSSProvider, error) {
	switch cfg.Provider {
	case "s3":
		return newS3OSS(cfg)
	default:
		return newAlibabaOSS(cfg)
	}
}

// NewOSSProviderFromDB obtiene la configuración activa y crea el provider
func NewOSSProviderFromDB() (OSSProvider, error) {
	cfg, err := models.GetLastActiveOssApi()
	if err != nil {
		return nil, fmt.Errorf("error obteniendo configuración OSS activa: %w", err)
	}
	return NewOSSProvider(cfg)
}

// NewOSSProviderByID obtiene una configuración por ID y crea el provider
func NewOSSProviderByID(id uint) (OSSProvider, error) {
	cfg, err := models.GetOssApiByID(id)
	if err != nil {
		return nil, fmt.Errorf("error obteniendo configuración OSS #%d: %w", id, err)
	}
	if !cfg.IsActive {
		return nil, fmt.Errorf("la configuración OSS #%d está inactiva", id)
	}
	return NewOSSProvider(cfg)
}
