package substrate

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

	"github.com/fxamacker/cbor/v2"
	"github.com/ipfs/go-cid"
	hoarderIface "github.com/taubyte/tau/core/services/hoarder"
	storageIface "github.com/taubyte/tau/core/services/substrate/components/storage"
	"github.com/taubyte/tau/p2p/peer"
	hoarderSpecs "github.com/taubyte/tau/pkg/specs/hoarder"
	"github.com/taubyte/tau/services/substrate/components/storage/common"
)

// Context Config will be generated by New()
func (s *Service) Storage(context storageIface.Context) (storageIface.Storage, error) {
	hash, err := common.GetStorageHash(context)
	if err != nil {
		return nil, err
	}

	s.storagesLock.RLock()
	storage, ok := s.storages[hash]
	s.storagesLock.RLocker().Unlock()

	if !ok {
		var commit, branch string
		context.Config, commit, branch, err = s.getStoreConfig(context.ProjectId, context.ApplicationId, context.Matcher)
		if err != nil {
			return nil, err
		}

		storage, err = s.storageMethod(s, s.dbFactory, context, common.Logger, s.matcher)
		if err != nil {
			return nil, err
		}

		s.storagesLock.Lock()
		s.storages[hash] = storage
		s.storagesLock.Unlock()

		err = s.pubsubStorage(context, branch)
		if err != nil {
			return nil, fmt.Errorf("pubsub storage `%s` failed with: %s", context.Matcher, err)
		}

		s.commitLock.Lock()
		s.commits[hash] = commit
		s.commitLock.Unlock()
	} else {
		valid, newCommitId, branch, err := s.validateCommit(hash, context.ProjectId) // TODO: is the commit on the same branch?
		if err != nil {
			return nil, err
		}

		if !valid {
			s.storagesLock.Lock()
			s.commitLock.Lock()

			defer s.storagesLock.Unlock()
			defer s.commitLock.Unlock()

			storage, err = s.updateStorage(storage, branch)
			if err != nil {
				return nil, err
			}

			s.storages[hash] = storage
			s.commits[hash] = newCommitId
		}
	}

	return storage, nil
}

func (s *Service) Add(content io.Reader) (cid.Cid, error) {
	__cid, err := s.Node().AddFile(content)
	if err != nil {
		return cid.Cid{}, fmt.Errorf("failed adding file with %v", err)
	}

	_cid, err := cid.Parse(__cid)
	if err != nil {
		return cid.Cid{}, fmt.Errorf("failed parsing cid with %v", err)
	}

	return _cid, nil
}

func (s *Service) GetFile(ctx context.Context, cid cid.Cid) (peer.ReadSeekCloser, error) {
	file, err := s.Node().GetFileFromCid(ctx, cid)
	if err != nil {
		return nil, fmt.Errorf("failed grabbing file %s from ipfs with %w", cid, err)
	}

	return file, nil
}
func (s *Service) pubsubStorage(ctx storageIface.Context, branch string) error {
	auction := &hoarderIface.Auction{
		Type:     hoarderIface.AuctionNew,
		MetaType: hoarderIface.Storage,
		Meta: hoarderIface.MetaData{
			ConfigId:      ctx.Config.Id,
			ApplicationId: ctx.ApplicationId,
			ProjectId:     ctx.ProjectId,
			Match:         ctx.Matcher,
			Branch:        branch,
		},
	}

	dataBytes, err := cbor.Marshal(auction)
	if err != nil {
		return fmt.Errorf("marshalling auction failed with %w", err)
	}

	pubsubCtx, pubsubCtxC := context.WithTimeout(s.Node().Context(), 10*time.Second)
	defer pubsubCtxC()

	if err = s.Node().PubSubPublish(pubsubCtx, hoarderSpecs.PubSubIdent, dataBytes); err != nil {
		return fmt.Errorf("failed publishing storage %s with %w", ctx.Matcher, err)
	}

	return nil
}
