package transfer

import (
	"context"
	"time"

	"github.com/apex/log"
	"github.com/mitchellh/colorstring"

	"github.com/Tech-Gamer/nwy-wings/server"
	"github.com/Tech-Gamer/nwy-wings/system"
)

// Status represents the current status of a transfer.
type Status string

// String satisfies the fmt.Stringer interface.
func (s Status) String() string {
	return string(s)
}

const (
	// StatusPending is the status of a transfer when it is first created.
	StatusPending Status = "pending"
	// StatusProcessing is the status of a transfer when it is currently in
	// progress, such as when the archive is being streamed to the target node.
	StatusProcessing Status = "processing"

	// StatusCancelling is the status of a transfer when it is in the process of
	// being cancelled.
	StatusCancelling Status = "cancelling"

	// StatusCancelled is the final status of a transfer when it has been
	// cancelled.
	StatusCancelled Status = "cancelled"
	// StatusFailed is the final status of a transfer when it has failed.
	StatusFailed Status = "failed"
	// StatusCompleted is the final status of a transfer when it has completed.
	StatusCompleted Status = "completed"
)

// Transfer represents a transfer of a server from one node to another.
type Transfer struct {
	// ctx is the context for the transfer.
	ctx context.Context
	// cancel is used to cancel all ongoing transfer operations for the server.
	cancel *context.CancelFunc

	// Server associated with the transfer.
	Server *server.Server
	// status of the transfer.
	status *system.Atomic[Status]

	// archive is the archive that is being created for the transfer.
	archive *Archive
}

// New returns a new transfer instance for the given server.
func New(ctx context.Context, s *server.Server) *Transfer {
	ctx, cancel := context.WithCancel(ctx)

	return &Transfer{
		ctx:    ctx,
		cancel: &cancel,

		Server: s,
		status: system.NewAtomic(StatusPending),
	}
}

// Context returns the context for the transfer.
func (t *Transfer) Context() context.Context {
	return t.ctx
}

// Cancel cancels the transfer.
func (t *Transfer) Cancel() {
	status := t.Status()
	if status == StatusCancelling ||
		status == StatusCancelled ||
		status == StatusCompleted ||
		status == StatusFailed {
		return
	}

	if t.cancel == nil {
		return
	}

	t.SetStatus(StatusCancelling)
	(*t.cancel)()
}

// Status returns the current status of the transfer.
func (t *Transfer) Status() Status {
	return t.status.Load()
}

// SetStatus sets the status of the transfer.
func (t *Transfer) SetStatus(s Status) {
	// TODO: prevent certain status changes from happening.
	// If we are cancelling, then we can't go back to processing.
	t.status.Store(s)

	t.Server.Events().Publish(server.TransferStatusEvent, s)
}

// SendMessage sends a message to the server's console.
func (t *Transfer) SendMessage(v string) {
	t.Server.Events().Publish(
		server.TransferLogsEvent,
		colorstring.Color("[yellow][bold]"+time.Now().Format(time.RFC1123)+" [Transfer System] [Source Node]:[default] "+v),
	)
}

// Error logs an error that occurred on the source node.
func (t *Transfer) Error(err error, v string) {
	t.Log().WithError(err).Error(v)
	t.SendMessage(v)
}

// Log returns a logger for the transfer.
func (t *Transfer) Log() *log.Entry {
	if t.Server == nil {
		return log.WithField("subsystem", "transfer")
	}
	return t.Server.Log().WithField("subsystem", "transfer")
}
