package memory

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	pathpkg "path"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
)

const (
	memoryMigrationMaxBackupReferences  = 64
	memoryMigrationMaxProductReferences = 512
	memoryMigrationMaxAncestorWalk      = 512
	memoryMigrationMaxAuthorBytes       = 255
	memoryMigrationMaxMessageBytes      = 64 << 10
)

// MemoryMigrationSource is the bounded legacy reader used only by explicit
// migration preflight/import. Ordinary Memory request paths never enumerate
// AGS history or refs.
type MemoryMigrationSource interface {
	ResolveMemoryMigrationHead(context.Context, repos.RepositoryIdentity, string) (string, error)
	ListMemoryMigrationBackups(context.Context, repos.RepositoryIdentity, int) ([]MemoryMigrationReference, error)
	ReadMemoryMigrationCommit(context.Context, repos.RepositoryIdentity, string) (MemoryMigrationCommit, error)
	ReadMemoryMigrationSnapshot(context.Context, repos.RepositoryIdentity, string, contentpayload.Limits) ([]MemoryMigrationFile, error)
}

type MemoryMigrationReference struct {
	Kind      string
	Label     string
	CommitSHA string
}

type MemoryMigrationCommit struct {
	CommitSHA      string
	FirstParentSHA string
	AuthorName     string
	AuthorEmail    string
	AuthoredAt     string
	Message        string
}

type MemoryMigrationFile struct {
	Path       string
	Data       []byte
	Executable bool
}

type MemoryMigrationPreflightRequest struct {
	Repository        repos.RepositoryIdentity
	Branch            string
	ProductReferences []MemoryMigrationReference
}

type MemoryMigrationPlanVersion struct {
	CommitSHA       string
	ParentCommitSHA string
	FirstParent     bool
	HistorySequence uint64
	AuthoredAt      time.Time
	RootDigest      string
	FileCount       int
	SizeBytes       int64
	Source          MemoryMigrationCommit
}

type MemoryMigrationPreflight struct {
	RepositoryID  string
	Branch        string
	HeadCommitSHA string
	Versions      []MemoryMigrationPlanVersion
	References    []MemoryMigrationReference
	VersionCount  uint64
	PayloadBytes  int64
	PinCount      uint64
}

// PreflightMemoryMigration inventories one exact branch first-parent chain and
// its bounded retained references before any Content Store or product write.
// It validates every retained full snapshot and never truncates an over-limit
// history, reference set, file tree, or quota total.
func PreflightMemoryMigration(ctx context.Context, source MemoryMigrationSource, request MemoryMigrationPreflightRequest) (MemoryMigrationPreflight, error) {
	if source == nil {
		return MemoryMigrationPreflight{}, fmt.Errorf("memory migration source is required")
	}
	repository := request.Repository
	repository.ID = strings.TrimSpace(repository.ID)
	branch := firstNonEmptyString(strings.TrimSpace(request.Branch), strings.TrimSpace(repository.DefaultBranch), "main")
	if repository.ID == "" || branch == "" || len(request.ProductReferences) > memoryMigrationMaxProductReferences {
		return MemoryMigrationPreflight{}, fmt.Errorf("memory migration source identity or reference set is invalid")
	}
	head, err := source.ResolveMemoryMigrationHead(ctx, repository, branch)
	if err != nil {
		return MemoryMigrationPreflight{}, err
	}
	head, err = normalizeMemoryMigrationSHA(head)
	if err != nil {
		return MemoryMigrationPreflight{}, fmt.Errorf("memory migration branch head: %w", err)
	}

	commits := make(map[string]MemoryMigrationCommit, memoryContentMaxVersions)
	newestFirst := make([]string, 0, memoryContentMaxVersions)
	current := head
	for current != "" {
		if len(newestFirst) >= int(memoryContentMaxVersions) {
			return MemoryMigrationPreflight{}, fmt.Errorf("memory migration first-parent history exceeds %d versions", memoryContentMaxVersions)
		}
		commit, err := readExactMemoryMigrationCommit(ctx, source, repository, current)
		if err != nil {
			return MemoryMigrationPreflight{}, err
		}
		if _, duplicate := commits[current]; duplicate {
			return MemoryMigrationPreflight{}, fmt.Errorf("memory migration first-parent history contains a cycle")
		}
		commits[current] = commit
		newestFirst = append(newestFirst, current)
		current = commit.FirstParentSHA
	}
	chain := make([]string, len(newestFirst))
	for index := range newestFirst {
		chain[len(newestFirst)-1-index] = newestFirst[index]
	}

	backups, err := source.ListMemoryMigrationBackups(ctx, repository, memoryMigrationMaxBackupReferences+1)
	if err != nil {
		return MemoryMigrationPreflight{}, err
	}
	if len(backups) > memoryMigrationMaxBackupReferences {
		return MemoryMigrationPreflight{}, fmt.Errorf("memory migration backup labels exceed %d pins", memoryMigrationMaxBackupReferences)
	}
	references, err := normalizeMemoryMigrationReferences(backups, request.ProductReferences)
	if err != nil {
		return MemoryMigrationPreflight{}, err
	}
	pinCount := 0
	for _, reference := range references {
		if reference.Kind == "backup" {
			pinCount++
		}
	}
	if pinCount > memoryMigrationMaxBackupReferences {
		return MemoryMigrationPreflight{}, fmt.Errorf("memory migration backup labels exceed %d pins", memoryMigrationMaxBackupReferences)
	}
	retained := make(map[string]struct{}, len(chain)+len(references))
	for _, sha := range chain {
		retained[sha] = struct{}{}
	}
	for _, reference := range references {
		retained[reference.CommitSHA] = struct{}{}
	}
	if len(retained) > int(memoryContentMaxVersions) {
		return MemoryMigrationPreflight{}, fmt.Errorf("memory migration retained set exceeds %d versions", memoryContentMaxVersions)
	}

	detached := make([]string, 0, len(retained)-len(chain))
	for sha := range retained {
		if _, onChain := commits[sha]; !onChain {
			detached = append(detached, sha)
		}
	}
	sort.Strings(detached)
	parents := make(map[string]string, len(retained))
	for index, sha := range chain {
		if index > 0 {
			parents[sha] = chain[index-1]
		}
	}
	for _, sha := range detached {
		commit, err := readExactMemoryMigrationCommit(ctx, source, repository, sha)
		if err != nil {
			return MemoryMigrationPreflight{}, err
		}
		commits[sha] = commit
		parents[sha], err = nearestRetainedMemoryMigrationAncestor(ctx, source, repository, commit.FirstParentSHA, sha, retained, commits)
		if err != nil {
			return MemoryMigrationPreflight{}, err
		}
	}
	orderedDetached, err := orderDetachedMemoryMigrationVersions(chain, detached, parents)
	if err != nil {
		return MemoryMigrationPreflight{}, err
	}
	ordered := append(append([]string(nil), chain...), orderedDetached...)
	versions, payloadBytes, err := buildMemoryMigrationPlanVersions(ctx, source, repository, ordered, len(chain), commits, parents)
	if err != nil {
		return MemoryMigrationPreflight{}, err
	}
	return MemoryMigrationPreflight{
		RepositoryID: repository.ID, Branch: branch, HeadCommitSHA: head,
		Versions: versions, References: references, VersionCount: uint64(len(versions)),
		PayloadBytes: payloadBytes, PinCount: uint64(pinCount),
	}, nil
}

func buildMemoryMigrationPlanVersions(ctx context.Context, source MemoryMigrationSource, repository repos.RepositoryIdentity, ordered []string, firstParentCount int, commits map[string]MemoryMigrationCommit, parents map[string]string) ([]MemoryMigrationPlanVersion, int64, error) {
	versions := make([]MemoryMigrationPlanVersion, 0, len(ordered))
	var payloadBytes int64
	for index, sha := range ordered {
		commit := commits[sha]
		firstParent := index < firstParentCount
		var historySequence uint64
		if firstParent {
			// Canonical first-parent history is always listed ahead of retained
			// detached product versions, newest first within each group.
			historySequence = uint64(len(ordered) - firstParentCount + index + 1)
		} else {
			historySequence = uint64(index - firstParentCount + 1)
		}
		files, err := source.ReadMemoryMigrationSnapshot(ctx, repository, sha, memoryContentTreeLimits)
		if err != nil {
			return nil, 0, err
		}
		digest, sizeBytes, err := validateMemoryMigrationSnapshot(files)
		if err != nil {
			return nil, 0, fmt.Errorf("memory migration commit %s: %w", sha, err)
		}
		if payloadBytes > memoryContentMaxStoredBytes-sizeBytes {
			return nil, 0, fmt.Errorf("memory migration retained payload exceeds %d bytes", memoryContentMaxStoredBytes)
		}
		payloadBytes += sizeBytes
		versions = append(versions, MemoryMigrationPlanVersion{
			CommitSHA: sha, ParentCommitSHA: parents[sha], FirstParent: firstParent,
			HistorySequence: historySequence,
			AuthoredAt:      memoryMigrationAuthoredAt(commit), RootDigest: digest, FileCount: len(files), SizeBytes: sizeBytes, Source: commit,
		})
	}
	return versions, payloadBytes, nil
}

func orderDetachedMemoryMigrationVersions(chain, detached []string, parents map[string]string) ([]string, error) {
	available := make(map[string]struct{}, len(chain)+len(detached))
	for _, sha := range chain {
		available[sha] = struct{}{}
	}
	remaining := append([]string(nil), detached...)
	ordered := make([]string, 0, len(detached))
	for len(remaining) > 0 {
		next := remaining[:0]
		progressed := false
		for _, sha := range remaining {
			parent := parents[sha]
			if parent == "" {
				ordered = append(ordered, sha)
				available[sha] = struct{}{}
				progressed = true
				continue
			}
			if _, ready := available[parent]; ready {
				ordered = append(ordered, sha)
				available[sha] = struct{}{}
				progressed = true
				continue
			}
			next = append(next, sha)
		}
		if !progressed {
			return nil, fmt.Errorf("memory migration detached retained ancestry is cyclic or incomplete")
		}
		remaining = next
	}
	return ordered, nil
}

func readExactMemoryMigrationCommit(ctx context.Context, source MemoryMigrationSource, repository repos.RepositoryIdentity, sha string) (MemoryMigrationCommit, error) {
	commit, err := source.ReadMemoryMigrationCommit(ctx, repository, sha)
	if err != nil {
		return MemoryMigrationCommit{}, err
	}
	commit.CommitSHA, err = normalizeMemoryMigrationSHA(commit.CommitSHA)
	if err != nil || commit.CommitSHA != sha {
		return MemoryMigrationCommit{}, fmt.Errorf("memory migration commit identity mismatch")
	}
	if strings.TrimSpace(commit.FirstParentSHA) != "" {
		commit.FirstParentSHA, err = normalizeMemoryMigrationSHA(commit.FirstParentSHA)
		if err != nil {
			return MemoryMigrationCommit{}, fmt.Errorf("memory migration first parent: %w", err)
		}
	}
	commit.AuthorName = strings.TrimSpace(commit.AuthorName)
	commit.AuthorEmail = strings.TrimSpace(commit.AuthorEmail)
	commit.AuthoredAt = strings.TrimSpace(commit.AuthoredAt)
	if !utf8.ValidString(commit.AuthorName) || !utf8.ValidString(commit.AuthorEmail) || !utf8.ValidString(commit.Message) ||
		len(commit.AuthorName) > memoryMigrationMaxAuthorBytes || len(commit.AuthorEmail) > memoryMigrationMaxAuthorBytes || len(commit.Message) > memoryMigrationMaxMessageBytes ||
		strings.ContainsRune(commit.AuthorName, '\x00') || strings.ContainsRune(commit.AuthorEmail, '\x00') || strings.ContainsRune(commit.Message, '\x00') {
		return MemoryMigrationCommit{}, fmt.Errorf("memory migration commit metadata exceeds bounds")
	}
	if memoryMigrationAuthoredAt(commit).IsZero() {
		return MemoryMigrationCommit{}, fmt.Errorf("memory migration commit authored timestamp is invalid")
	}
	return commit, nil
}

func memoryMigrationAuthoredAt(commit MemoryMigrationCommit) time.Time {
	authoredAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(commit.AuthoredAt))
	if err != nil {
		return time.Time{}
	}
	return authoredAt.UTC()
}

func normalizeMemoryMigrationReferences(groups ...[]MemoryMigrationReference) ([]MemoryMigrationReference, error) {
	result := make([]MemoryMigrationReference, 0)
	seen := make(map[string]struct{})
	for _, group := range groups {
		for _, reference := range group {
			reference.Kind = strings.TrimSpace(reference.Kind)
			reference.Label = strings.TrimSpace(reference.Label)
			sha, err := normalizeMemoryMigrationSHA(reference.CommitSHA)
			if err != nil || reference.Kind == "" || reference.Label == "" {
				return nil, fmt.Errorf("memory migration reference is invalid")
			}
			reference.CommitSHA = sha
			key := reference.Kind + "\x00" + reference.Label
			if _, duplicate := seen[key]; duplicate {
				return nil, fmt.Errorf("memory migration reference %q is duplicated", reference.Label)
			}
			seen[key] = struct{}{}
			result = append(result, reference)
		}
	}
	sort.Slice(result, func(i, j int) bool {
		if result[i].Kind != result[j].Kind {
			return result[i].Kind < result[j].Kind
		}
		return result[i].Label < result[j].Label
	})
	return result, nil
}

func nearestRetainedMemoryMigrationAncestor(ctx context.Context, source MemoryMigrationSource, repository repos.RepositoryIdentity, parent, target string, retained map[string]struct{}, cache map[string]MemoryMigrationCommit) (string, error) {
	current := parent
	seen := map[string]struct{}{target: {}}
	for steps := 0; current != ""; steps++ {
		if steps >= memoryMigrationMaxAncestorWalk {
			return "", fmt.Errorf("memory migration detached ancestor walk exceeds %d commits", memoryMigrationMaxAncestorWalk)
		}
		if _, cycle := seen[current]; cycle {
			return "", fmt.Errorf("memory migration detached ancestry contains a cycle")
		}
		seen[current] = struct{}{}
		if _, ok := retained[current]; ok {
			return current, nil
		}
		commit, ok := cache[current]
		if !ok {
			var err error
			commit, err = readExactMemoryMigrationCommit(ctx, source, repository, current)
			if err != nil {
				return "", err
			}
			cache[current] = commit
		}
		current = commit.FirstParentSHA
	}
	return "", nil
}

func validateMemoryMigrationSnapshot(files []MemoryMigrationFile) (string, int64, error) {
	if len(files) == 0 || len(files) > memoryContentMaxFiles {
		return "", 0, fmt.Errorf("snapshot file count exceeds bounds")
	}
	files = append([]MemoryMigrationFile(nil), files...)
	sort.Slice(files, func(i, j int) bool { return strings.TrimSpace(files[i].Path) < strings.TrimSpace(files[j].Path) })
	entries := make([]contentpayload.ManifestEntry, 0, len(files))
	seen := make(map[string]struct{}, len(files))
	var total int64
	for _, file := range files {
		path := strings.TrimSpace(file.Path)
		size := int64(len(file.Data))
		if !validMemoryMigrationPath(file.Path) {
			return "", 0, fmt.Errorf("snapshot contains an invalid path")
		}
		if _, duplicate := seen[path]; duplicate {
			return "", 0, fmt.Errorf("snapshot contains a normalization collision")
		}
		seen[path] = struct{}{}
		if size > memoryContentMaxFileBytes || total > memoryContentMaxTotalBytes-size {
			return "", 0, fmt.Errorf("snapshot exceeds byte bounds")
		}
		if !utf8.Valid(file.Data) || bytes.IndexByte(file.Data, 0) >= 0 {
			return "", 0, fmt.Errorf("snapshot contains non-text content")
		}
		sum := sha256.Sum256(file.Data)
		entries = append(entries, contentpayload.ManifestEntry{Path: path, Digest: contentpayload.Digest(hex.EncodeToString(sum[:])), SizeBytes: size, Executable: file.Executable})
		total += size
	}
	encoded, err := json.Marshal(entries)
	if err != nil {
		return "", 0, err
	}
	sum := sha256.Sum256(encoded)
	manifest, err := json.Marshal(contentpayload.Manifest{
		WorkspaceID: strings.Repeat("w", 64), ContentID: strings.Repeat("c", 64), VersionID: strings.Repeat("v", 64),
		RootDigest: contentpayload.Digest(hex.EncodeToString(sum[:])), SizeBytes: total, Files: entries,
	})
	if err != nil {
		return "", 0, err
	}
	if int64(len(manifest)) > memoryContentMaxManifestBytes {
		return "", 0, fmt.Errorf("snapshot manifest exceeds bounds")
	}
	return hex.EncodeToString(sum[:]), total, nil
}

func validMemoryMigrationPath(value string) bool {
	path := strings.TrimSpace(value)
	return path != "" && path == value && pathpkg.Clean(path) == path && !pathpkg.IsAbs(path) && path != "." && !strings.HasPrefix(path, "../") && !strings.Contains(path, "\\") && !strings.ContainsRune(path, '\x00')
}

func normalizeMemoryMigrationSHA(value string) (string, error) {
	value = strings.ToLower(strings.TrimSpace(value))
	if len(value) != 40 {
		return "", fmt.Errorf("commit sha must contain 40 hexadecimal characters")
	}
	if _, err := hex.DecodeString(value); err != nil {
		return "", fmt.Errorf("commit sha is invalid")
	}
	return value, nil
}
