package assets

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type OSSUtilPublisher struct {
	ossutilPath   string
	configFile    string
	bucketRoot    string
	publicBaseURL string
}

func NewOSSUtilPublisher(ossutilPath, configFile, bucketRoot, publicBaseURL string) *OSSUtilPublisher {
	return &OSSUtilPublisher{
		ossutilPath:   strings.TrimSpace(ossutilPath),
		configFile:    strings.TrimSpace(configFile),
		bucketRoot:    strings.TrimRight(strings.TrimSpace(bucketRoot), "/"),
		publicBaseURL: strings.TrimRight(strings.TrimSpace(publicBaseURL), "/"),
	}
}

func (p *OSSUtilPublisher) Enabled() bool {
	return p != nil &&
		p.ossutilPath != "" &&
		p.configFile != "" &&
		p.bucketRoot != "" &&
		p.publicBaseURL != ""
}

func (p *OSSUtilPublisher) UploadJSON(ctx context.Context, publicURL string, payload []byte) error {
	if !p.Enabled() {
		return fmt.Errorf("asset publisher is not configured")
	}
	if len(payload) == 0 {
		return fmt.Errorf("payload is empty")
	}

	objectKey, err := p.objectKeyFromPublicURL(publicURL)
	if err != nil {
		return err
	}

	if _, err := os.Stat(p.ossutilPath); err != nil {
		return fmt.Errorf("ossutil not found: %w", err)
	}
	if _, err := os.Stat(p.configFile); err != nil {
		return fmt.Errorf("ossutil config not found: %w", err)
	}

	tmpFile, err := os.CreateTemp("", "cmr-manifest-*.json")
	if err != nil {
		return fmt.Errorf("create temp file: %w", err)
	}
	tmpPath := tmpFile.Name()
	defer os.Remove(tmpPath)

	if _, err := tmpFile.Write(payload); err != nil {
		tmpFile.Close()
		return fmt.Errorf("write temp file: %w", err)
	}
	if err := tmpFile.Close(); err != nil {
		return fmt.Errorf("close temp file: %w", err)
	}

	target := p.bucketRoot + "/" + objectKey
	cmd := exec.CommandContext(ctx, p.ossutilPath, "cp", "-f", tmpPath, target, "--config-file", p.configFile)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("upload object %s failed: %w: %s", objectKey, err, strings.TrimSpace(string(output)))
	}

	return nil
}

func (p *OSSUtilPublisher) objectKeyFromPublicURL(publicURL string) (string, error) {
	publicURL = strings.TrimSpace(publicURL)
	if publicURL == "" {
		return "", fmt.Errorf("public url is required")
	}
	if !strings.HasPrefix(publicURL, p.publicBaseURL+"/") {
		return "", fmt.Errorf("public url %s does not match public base %s", publicURL, p.publicBaseURL)
	}
	relative := strings.TrimPrefix(publicURL, p.publicBaseURL+"/")
	relative = strings.ReplaceAll(relative, "\\", "/")
	relative = strings.TrimLeft(relative, "/")
	if relative == "" {
		return "", fmt.Errorf("public url %s resolved to empty object key", publicURL)
	}
	return filepath.ToSlash(relative), nil
}
