package storage

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"time"

	"go.ipao.vip/atom/container"
	"go.ipao.vip/atom/opt"
)

const DefaultPrefix = "Storage"

func DefaultProvider() container.ProviderContainer {
	return container.ProviderContainer{
		Provider: Provide,
		Options: []opt.Option{
			opt.Prefix(DefaultPrefix),
		},
	}
}

func Provide(opts ...opt.Option) error {
	o := opt.New(opts...)
	var config Config
	if err := o.UnmarshalConfig(&config); err != nil {
		return err
	}
	return container.Container.Provide(func() (*Storage, error) {
		return &Storage{Config: &config}, nil
	}, o.DiOptions()...)
}

type Storage struct {
	Config *Config
}

func (s *Storage) Delete(key string) error {
	if s.Config.Type == "local" {
		localPath := s.Config.LocalPath
		if localPath == "" {
			localPath = "./storage"
		}
		path := filepath.Join(localPath, key)
		return os.Remove(path)
	}
	// TODO: S3 implementation
	return nil
}

func (s *Storage) SignURL(method, key string, expires time.Duration) (string, error) {
	exp := time.Now().Add(expires).Unix()
	sign := s.signature(method, key, exp)

	baseURL := s.Config.BaseURL
	// Ensure BaseURL doesn't end with slash if we add one
	// Simplified: assume standard /v1/storage prefix in BaseURL or append it
	// We'll append /<key>

	u, err := url.Parse(baseURL + "/" + key)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Set("expires", strconv.FormatInt(exp, 10))
	q.Set("sign", sign)
	u.RawQuery = q.Encode()

	return u.String(), nil
}

func (s *Storage) Verify(method, key, expStr, sign string) error {
	exp, err := strconv.ParseInt(expStr, 10, 64)
	if err != nil {
		return fmt.Errorf("invalid expiry")
	}
	if time.Now().Unix() > exp {
		return fmt.Errorf("expired")
	}

	expected := s.signature(method, key, exp)
	if !hmac.Equal([]byte(expected), []byte(sign)) {
		return fmt.Errorf("invalid signature")
	}
	return nil
}

func (s *Storage) signature(method, key string, exp int64) string {
	str := fmt.Sprintf("%s\n%s\n%d", method, key, exp)
	h := hmac.New(sha256.New, []byte(s.Config.Secret))
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}
