package urchin

import (
	"context"
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/url"
	"strconv"
	"strings"

	"code.gitea.io/gitea/modules/urfs_client/config"
	urfs "code.gitea.io/gitea/modules/urfs_client/dfstore"
)

type Urchinfs interface {

	//// schedule source dataset to target peer
	//ScheduleDataToPeer(sourceUrl, destPeerHost string) (*PeerResult, error)
	//
	//// check schedule data to peer task status
	//CheckScheduleTaskStatus(sourceUrl, destPeerHost string) (*PeerResult, error)

	ScheduleDataToPeerByKey(endpoint, bucketName, objectKey, destPeerHost string) (*PeerResult, error)

	CheckScheduleTaskStatusByKey(endpoint, bucketName, objectKey, destPeerHost string) (*PeerResult, error)
}

type urchinfs struct {
	// Initialize default urfs config.
	cfg *config.DfstoreConfig
}

// New urchinfs instance.
func New() Urchinfs {

	urfs := &urchinfs{
		cfg: config.NewDfstore(),
	}
	return urfs
}

const (
	// UrfsScheme if the scheme of object storage.
	UrfsScheme = "urfs"
)

/*
func (urfs *urchinfs) ScheduleDataToPeer(sourceUrl, destPeerHost string) (*PeerResult, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	if err := urfs.cfg.Validate(); err != nil {
		return nil, err
	}

	if err := validateSchedulelArgs(sourceUrl, destPeerHost); err != nil {
		return nil, err
	}

	// Copy object storage to local file.
	endpoint, bucketName, objectKey, err := parseUrfsURL(sourceUrl)
	if err != nil {
		return nil, err
	}
	peerResult, err := processScheduleDataToPeer(ctx, urfs.cfg, endpoint, bucketName, objectKey, destPeerHost)
	if err != nil {
		return nil, err
	}

	return peerResult, err
}

*/

func (urfs *urchinfs) ScheduleDataToPeerByKey(endpoint, bucketName, objectKey, destPeerHost string) (*PeerResult, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	peerResult, err := processScheduleDataToPeer(ctx, urfs.cfg, endpoint, bucketName, objectKey, destPeerHost)
	if err != nil {
		return nil, err
	}

	return peerResult, err
}

/*
func (urfs *urchinfs) CheckScheduleTaskStatus(sourceUrl, destPeerHost string) (*PeerResult, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	if err := urfs.cfg.Validate(); err != nil {
		return nil, err
	}

	if err := validateSchedulelArgs(sourceUrl, destPeerHost); err != nil {
		return nil, err
	}

	// Copy object storage to local file.
	endpoint, bucketName, objectKey, err := parseUrfsURL(sourceUrl)
	if err != nil {
		return nil, err
	}
	peerResult, err := processCheckScheduleTaskStatus(ctx, urfs.cfg, endpoint, bucketName, objectKey, destPeerHost)
	if err != nil {
		return nil, err
	}

	return peerResult, err
}

*/

func (urfs *urchinfs) CheckScheduleTaskStatusByKey(endpoint, bucketName, objectKey, destPeerHost string) (*PeerResult, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	peerResult, err := processCheckScheduleTaskStatus(ctx, urfs.cfg, endpoint, bucketName, objectKey, destPeerHost)
	if err != nil {
		return nil, err
	}

	return peerResult, err
}

// isUrfsURL determines whether the raw url is urfs url.
func isUrfsURL(rawURL string) bool {
	u, err := url.ParseRequestURI(rawURL)
	if err != nil {
		return false
	}

	if u.Scheme != UrfsScheme || u.Host == "" || u.Path == "" {
		return false
	}

	return true
}

// Validate copy arguments.
func validateSchedulelArgs(sourceUrl, destPeer string) error {
	if !isUrfsURL(sourceUrl) {
		return errors.New("source url should be urfs:// protocol")
	}

	return nil
}

/*
// Parse object storage url. eg: urfs://源数据$endpoint/源数据$bucket/源数据filepath
func parseUrfsURL(rawURL string) (string, string, string, error) {
	u, err := url.ParseRequestURI(rawURL)
	if err != nil {
		return "", "", "", err
	}

	if u.Scheme != UrfsScheme {
		return "", "", "", fmt.Errorf("invalid scheme, e.g. %s://endpoint/bucket_name/object_key", UrfsScheme)
	}

	if u.Host == "" {
		return "", "", "", errors.New("empty endpoint name")
	}

	if u.Path == "" {
		return "", "", "", errors.New("empty object path")
	}

	bucket, key, found := strings.Cut(strings.Trim(u.Path, "/"), "/")
	if found == false {
		return "", "", "", errors.New("invalid bucket and object key " + u.Path)
	}

	return u.Host, bucket, key, nil
}

*/

// Schedule object storage to peer.
func processScheduleDataToPeer(ctx context.Context, cfg *config.DfstoreConfig, endpoint, bucketName, objectKey, dstPeer string) (*PeerResult, error) {
	dfs := urfs.New(cfg.Endpoint)
	meta, err := dfs.GetUrfsMetadataWithContext(ctx, &urfs.GetUrfsMetadataInput{
		Endpoint:   endpoint,
		BucketName: bucketName,
		ObjectKey:  objectKey,
		DstPeer:    dstPeer,
	})
	if err != nil {
		return nil, err
	}

	reader, err := dfs.GetUrfsWithContext(ctx, &urfs.GetUrfsInput{
		Endpoint:   endpoint,
		BucketName: bucketName,
		ObjectKey:  objectKey,
		DstPeer:    dstPeer,
	})
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	body, err := ioutil.ReadAll(reader)

	var peerResult PeerResult
	if err == nil {
		err = json.Unmarshal((body), &peerResult)
	}
	peerResult.SignedUrl = strings.ReplaceAll(peerResult.SignedUrl, "\\u0026", "&")

	fileContentLength, err := strconv.ParseInt(peerResult.ContentLength, 10, 64)
	if err != nil {
		return nil, err
	}
	if fileContentLength != meta.ContentLength {
		return nil, errors.New("content length inconsistent with meta")
	}

	return &peerResult, err
}

// check schedule task status.
func processCheckScheduleTaskStatus(ctx context.Context, cfg *config.DfstoreConfig, endpoint, bucketName, objectKey, dstPeer string) (*PeerResult, error) {
	dfs := urfs.New(cfg.Endpoint)
	meta, err := dfs.GetUrfsMetadataWithContext(ctx, &urfs.GetUrfsMetadataInput{
		Endpoint:   endpoint,
		BucketName: bucketName,
		ObjectKey:  objectKey,
		DstPeer:    dstPeer,
	})
	if err != nil {
		return nil, err
	}

	reader, err := dfs.GetUrfsStatusWithContext(ctx, &urfs.GetUrfsInput{
		Endpoint:   endpoint,
		BucketName: bucketName,
		ObjectKey:  objectKey,
		DstPeer:    dstPeer,
	})
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	body, err := ioutil.ReadAll(reader)

	var peerResult PeerResult
	if err == nil {
		err = json.Unmarshal((body), &peerResult)
	}
	peerResult.SignedUrl = strings.ReplaceAll(peerResult.SignedUrl, "\\u0026", "&")

	fileContentLength, err := strconv.ParseInt(peerResult.ContentLength, 10, 64)
	if err != nil {
		return nil, err
	}
	if fileContentLength != meta.ContentLength {
		return nil, err
	}
	return &peerResult, err
}

type PeerResult struct {
	ContentType   string `json:"Content-Type"`
	ContentLength string `json:"Content-Length"`
	SignedUrl     string
	DataRoot      string
	DataPath      string
	DataEndpoint  string
	StatusCode    int
	StatusMsg     string
	TaskID        string
}
