package memorywatcher

import (
	"context"
	"errors"
	"time"

	gittransport "github.com/go-git/go-git/v5/plumbing/transport"
	"github.com/tidbcloud/AgentTeam/desktop/internal/vault"
)

// CloneCommitter adapts a *vault.Clone to the watcher's Committer interface.
// The vault package returns vault.ErrNothingToCommit on a clean worktree;
// the adapter translates it to memorywatcher.ErrNothingToCommit so the
// watcher package does not import vault.
type CloneCommitter struct {
	Clone *vault.Clone
}

// NewCloneCommitter is the constructor; panics on nil to fail fast at wiring
// time rather than at the first commit attempt.
func NewCloneCommitter(c *vault.Clone) *CloneCommitter {
	if c == nil {
		panic("memorywatcher: nil Clone")
	}
	return &CloneCommitter{Clone: c}
}

// CommitPending delegates to vault.(*Clone).CommitPending and translates the
// nothing-to-commit sentinel. Author name is forwarded as the commit author
// when non-empty.
func (a *CloneCommitter) CommitPending(ctx context.Context, msg string, opts CommitOpts) (string, error) {
	var author *vault.Signature
	if opts.AuthorName != "" || opts.AuthorEmail != "" {
		author = &vault.Signature{Name: opts.AuthorName, Email: opts.AuthorEmail}
	}
	hash, err := a.Clone.CommitPending(ctx, msg, vault.CommitPendingOpts{
		Amend:  opts.Amend,
		Author: author,
	})
	if err == nil {
		return hash, nil
	}
	if errors.Is(err, vault.ErrNothingToCommit) {
		return "", ErrNothingToCommit
	}
	return "", err
}

// LastCommitMessage delegates to vault.(*Clone).LastCommitMessage unchanged.
func (a *CloneCommitter) LastCommitMessage(ctx context.Context) (string, error) {
	if a == nil || a.Clone == nil {
		return "", errors.New("memorywatcher: nil Clone")
	}
	return a.Clone.LastCommitMessage(ctx)
}

// LastCommitTime delegates to vault.(*Clone).LastCommitTime unchanged.
func (a *CloneCommitter) LastCommitTime(ctx context.Context) (time.Time, error) {
	if a == nil || a.Clone == nil {
		return time.Time{}, errors.New("memorywatcher: nil Clone")
	}
	return a.Clone.LastCommitTime(ctx)
}

// ClonePusher adapts a *vault.Clone to the watcher's Pusher interface,
// translating vault.ErrNothingToPush to the local sentinel so the watcher
// package does not import vault. The adapter intentionally wraps the same
// Clone pointer CloneCommitter uses — Push operates on the same bound
// branch the watcher just committed to.
type ClonePusher struct {
	Clone *vault.Clone
	// Auth is forwarded to vault.Push on every call. The dispatcher holds the
	// AGS repository sync lease at session scope and passes it here so vault
	// stays auth-free.
	Auth gittransport.AuthMethod
}

// NewClonePusher is the constructor; panics on nil Clone to fail fast at
// wiring time. Auth may be nil for file:// test origins.
func NewClonePusher(c *vault.Clone, auth gittransport.AuthMethod) *ClonePusher {
	if c == nil {
		panic("memorywatcher: nil Clone")
	}
	return &ClonePusher{Clone: c, Auth: auth}
}

// Push delegates to vault.(*Clone).Push and translates the nothing-to-push
// sentinel. Force push is enabled because the committer may amend commits
// (same context → amend key), which rewrites the branch head.
func (p *ClonePusher) Push(ctx context.Context, opts PushOpts) (string, error) {
	hash, err := p.Clone.Push(ctx, vault.PushOpts{Auth: p.Auth, Force: true})
	if err == nil {
		return hash, nil
	}
	if errors.Is(err, vault.ErrNothingToPush) {
		return "", ErrNothingToPush
	}
	return "", err
}

// CloneSyncer adapts a *vault.Clone to the watcher's Syncer interface.
// It fetches origin and compares local HEAD with origin/<branch> so the
// watcher can decide whether to amend or create a new commit.
type CloneSyncer struct {
	Clone *vault.Clone
	Auth  gittransport.AuthMethod
}

// NewCloneSyncer is the constructor; panics on nil Clone to fail fast at
// wiring time. Auth may be nil for file:// test origins.
func NewCloneSyncer(c *vault.Clone, auth gittransport.AuthMethod) *CloneSyncer {
	if c == nil {
		panic("memorywatcher: nil Clone")
	}
	return &CloneSyncer{Clone: c, Auth: auth}
}

// Sync delegates to vault.(*Clone).Sync.
func (s *CloneSyncer) Sync(ctx context.Context) (SyncResult, error) {
	result, err := s.Clone.Sync(ctx, vault.SyncOpts{Auth: s.Auth})
	if err != nil {
		return SyncResult{}, err
	}
	return SyncResult{State: translateSyncState(result.State)}, nil
}

func translateSyncState(s vault.SyncState) SyncState {
	switch s {
	case vault.SyncUpToDate:
		return SyncUpToDate
	case vault.SyncLocalAhead:
		return SyncLocalAhead
	case vault.SyncRemoteAhead:
		return SyncRemoteAhead
	case vault.SyncDiverged:
		return SyncDiverged
	default:
		return SyncUpToDate
	}
}
