//go:build windows

package hcsv2

import (
	"context"
	"encoding/json"
	"errors"
	"syscall"
	"time"

	"github.com/Microsoft/hcsshim/internal/computecore"
	"github.com/Microsoft/hcsshim/internal/hcs/resourcepaths"
	hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
	"github.com/Microsoft/hcsshim/internal/ot"

	"go.opentelemetry.io/otel/attribute"
)

// MigrationConfig holds parameters for starting a compute system as a live migration
// destination, or for initiating the source side of a live migration.
type MigrationConfig struct {
	// Socket is the handle to the live migration transport socket.
	Socket syscall.Handle
	// SessionID identifies the migration session.
	SessionID uint32
}

// StartWithMigrationOptions synchronously starts the compute system as a live
// migration destination using the provided configuration.
func (computeSystem *System) StartWithMigrationOptions(ctx context.Context, config *MigrationConfig) (err error) {
	if config == nil {
		return errors.New("live migration config must not be nil")
	}

	operation := "hcs::System::Start"

	ctx, span := ot.StartSpan(ctx, operation)
	defer span.End()
	defer func() { ot.SetSpanStatus(span, err) }()
	span.SetAttributes(attribute.String("cid", computeSystem.id))

	computeSystem.handleLock.Lock()
	defer computeSystem.handleLock.Unlock()

	if computeSystem.handle == 0 {
		return makeSystemError(computeSystem, operation, ErrAlreadyClosed)
	}

	opts, err := json.Marshal(hcsschema.StartOptions{
		DestinationMigrationOptions: &hcsschema.MigrationStartOptions{
			NetworkSettings: &hcsschema.MigrationNetworkSettings{SessionID: config.SessionID},
		},
	})
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	_, callErr := runOperation(ctx, func(op computecore.HcsOperation) error {
		if err := computecore.HcsAddResourceToOperation(ctx, op, computecore.HcsResourceTypeSocket, resourcepaths.LiveMigrationSocketURI, config.Socket); err != nil {
			return err
		}
		return computecore.HcsStartComputeSystem(ctx, computeSystem.handle, op, string(opts))
	})
	if callErr != nil {
		return makeSystemError(computeSystem, operation, callErr)
	}
	computeSystem.startTime = time.Now()
	return nil
}

// InitializeLiveMigrationOnSource prepares the source compute system for a
// live migration. Must be called on the source before StartLiveMigrationOnSource.
func (computeSystem *System) InitializeLiveMigrationOnSource(ctx context.Context, options *hcsschema.MigrationInitializeOptions) (err error) {
	operation := "hcs::System::InitializeLiveMigrationOnSource"

	ctx, span := ot.StartSpan(ctx, operation)
	defer span.End()
	defer func() { ot.SetSpanStatus(span, err) }()
	span.SetAttributes(attribute.String("cid", computeSystem.id))

	computeSystem.handleLock.Lock()
	defer computeSystem.handleLock.Unlock()

	if computeSystem.handle == 0 {
		return makeSystemError(computeSystem, operation, ErrAlreadyClosed)
	}

	if options == nil {
		options = &hcsschema.MigrationInitializeOptions{}
	}
	optionsJSON, err := json.Marshal(options)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	op, err := computecore.HcsCreateOperation(ctx, 0, 0)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	defer computecore.HcsCloseOperation(ctx, op)

	// Issue the initialize call and wait for completion.
	if err = computecore.HcsInitializeLiveMigrationOnSource(ctx, computeSystem.handle, op, string(optionsJSON)); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	if _, err = computecore.HcsWaitForOperationResult(ctx, op, 0xFFFFFFFF); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	return nil
}

// StartLiveMigrationOnSource begins the source-side migration using the given
// transport socket and session ID. Blocks until HCS accepts the start;
// transfer progress is observed via MigrationNotifications.
func (computeSystem *System) StartLiveMigrationOnSource(ctx context.Context, config *MigrationConfig) (err error) {
	if config == nil {
		return errors.New("migration config must not be nil")
	}

	operation := "hcs::System::StartLiveMigrationOnSource"

	ctx, span := ot.StartSpan(ctx, operation)
	defer span.End()
	defer func() { ot.SetSpanStatus(span, err) }()
	span.SetAttributes(attribute.String("cid", computeSystem.id))

	computeSystem.handleLock.Lock()
	defer computeSystem.handleLock.Unlock()

	if computeSystem.handle == 0 {
		return makeSystemError(computeSystem, operation, ErrAlreadyClosed)
	}

	op, err := computecore.HcsCreateOperation(ctx, 0, 0)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	defer computecore.HcsCloseOperation(ctx, op)

	// Attach the migration socket to the operation before starting.
	if err := computecore.HcsAddResourceToOperation(ctx, op, computecore.HcsResourceTypeSocket, resourcepaths.LiveMigrationSocketURI, config.Socket); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	options := hcsschema.MigrationStartOptions{
		NetworkSettings: &hcsschema.MigrationNetworkSettings{SessionID: config.SessionID},
	}
	optionsJSON, err := json.Marshal(options)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	// Issue the start call and wait for completion.
	if err := computecore.HcsStartLiveMigrationOnSource(ctx, computeSystem.handle, op, string(optionsJSON)); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	if _, err := computecore.HcsWaitForOperationResult(ctx, op, 0xFFFFFFFF); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	return nil
}

// StartLiveMigrationTransfer starts the memory transfer phase of a live migration.
func (computeSystem *System) StartLiveMigrationTransfer(ctx context.Context, options *hcsschema.MigrationTransferOptions) (err error) {
	operation := "hcs::System::StartLiveMigrationTransfer"

	ctx, span := ot.StartSpan(ctx, operation)
	defer span.End()
	defer func() { ot.SetSpanStatus(span, err) }()
	span.SetAttributes(attribute.String("cid", computeSystem.id))

	computeSystem.handleLock.Lock()
	defer computeSystem.handleLock.Unlock()

	if computeSystem.handle == 0 {
		return makeSystemError(computeSystem, operation, ErrAlreadyClosed)
	}

	if options == nil {
		options = &hcsschema.MigrationTransferOptions{}
	}
	optionsJSON, err := json.Marshal(options)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	op, err := computecore.HcsCreateOperation(ctx, 0, 0)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	defer computecore.HcsCloseOperation(ctx, op)

	// Begin the memory transfer and wait for completion.
	if err := computecore.HcsStartLiveMigrationTransfer(ctx, computeSystem.handle, op, string(optionsJSON)); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	if _, err := computecore.HcsWaitForOperationResult(ctx, op, 0xFFFFFFFF); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	return nil
}

// FinalizeLiveMigration completes the live migration workflow.
func (computeSystem *System) FinalizeLiveMigration(ctx context.Context, opts *hcsschema.MigrationFinalizedOptions) (err error) {
	operation := "hcs::System::FinalizeLiveMigration"

	ctx, span := ot.StartSpan(ctx, operation)
	defer span.End()
	defer func() { ot.SetSpanStatus(span, err) }()
	span.SetAttributes(attribute.String("cid", computeSystem.id))

	computeSystem.handleLock.Lock()
	defer computeSystem.handleLock.Unlock()

	if computeSystem.handle == 0 {
		return makeSystemError(computeSystem, operation, ErrAlreadyClosed)
	}

	if opts == nil {
		opts = &hcsschema.MigrationFinalizedOptions{}
	}
	optionsJSON, err := json.Marshal(opts)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	op, err := computecore.HcsCreateOperation(ctx, 0, 0)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	defer computecore.HcsCloseOperation(ctx, op)

	// Finalize the migration and wait for completion.
	if err := computecore.HcsFinalizeLiveMigration(ctx, computeSystem.handle, op, string(optionsJSON)); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	if _, err := computecore.HcsWaitForOperationResult(ctx, op, 0xFFFFFFFF); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	return nil
}

// CancelLiveMigration requests cancellation of an in-flight live migration.
// It deliberately takes no handleLock so it can interrupt an in-flight transfer that holds it.
func (computeSystem *System) CancelLiveMigration(ctx context.Context, options *hcsschema.MigrationCancelOptions) (err error) {
	operation := "hcs::System::CancelLiveMigration"

	ctx, span := ot.StartSpan(ctx, operation)
	defer span.End()
	defer func() { ot.SetSpanStatus(span, err) }()
	span.SetAttributes(attribute.String("cid", computeSystem.id))

	if computeSystem.handle == 0 {
		return makeSystemError(computeSystem, operation, ErrAlreadyClosed)
	}

	if options == nil {
		options = &hcsschema.MigrationCancelOptions{}
	}
	optionsJSON, err := json.Marshal(options)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}

	op, err := computecore.HcsCreateOperation(ctx, 0, 0)
	if err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	defer computecore.HcsCloseOperation(ctx, op)

	// Issue the cancel and wait for completion.
	if err := computecore.HcsCancelLiveMigration(ctx, computeSystem.handle, op, string(optionsJSON)); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	if _, err := computecore.HcsWaitForOperationResult(ctx, op, 0xFFFFFFFF); err != nil {
		return makeSystemError(computeSystem, operation, err)
	}
	return nil
}

// MigrationNotifications returns a read-only channel of live migration events
// for this System. The channel exists for the System's lifetime and is safe
// to subscribe to before any migration call, so callers do not miss early
// events such as SetupDone.
//
// The channel is never closed; callers signal end-of-stream via their own
// context. Sends are non-blocking (buffer size migrationNotificationBufferSize)
// and events are dropped on overflow, so consumers must drain promptly.
func (computeSystem *System) MigrationNotifications() <-chan hcsschema.OperationSystemMigrationNotificationInfo {
	return computeSystem.migrationNotifyCh
}
