package automation

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	contentstoreservice "github.com/tidbcloud/AgentTeam/internal/service/content"
	contentmigrationservice "github.com/tidbcloud/AgentTeam/internal/service/content/migration"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
	contentstore "github.com/tidbcloud/AgentTeam/internal/store/content"
	contentmigrationstore "github.com/tidbcloud/AgentTeam/internal/store/content/migration"
	resourcerepostore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

type automationContentMigrationWorker interface {
	RunExact(context.Context, contentmigrationservice.Request, contentmigrationservice.Exporter, contentmigrationservice.Projector) (contentmigrationservice.Result, error)
}

type AutomationContentAuthorityActivator interface {
	AutomationContentAuthority
	AcquireLegacyAuthorityFence(context.Context, contentstore.AcquireLegacyAuthorityFenceParams) (contentstore.LegacyAuthorityFence, error)
	ReleaseLegacyAuthorityFence(context.Context, contentstore.LegacyAuthorityFence, time.Time) error
	ActivateNativeAuthority(context.Context, contentstore.ActivateNativeAuthorityParams) (contentstore.Authority, error)
}

type AutomationContentMigrationExporter interface {
	ExportExactAutomationRepository(context.Context, string, string, contentpayload.Limits) (contentmigrationservice.Export, error)
}

type AutomationContentMigrationMappingStore interface {
	GetMapping(context.Context, string, string) (contentmigrationstore.Mapping, error)
}

type AutomationContentMigrationVersionStore interface {
	FindContentByBinding(context.Context, string, string, string) (contentstoreservice.Content, bool, error)
	GetVersion(context.Context, string, string, string) (contentstoreservice.Version, error)
}

type AutomationContentCutoverStore interface {
	RecordLegacyContentPublication(context.Context, automationstore.AutomationContentPublication, []automationstore.AutomationContentDefinition, string, automationstore.ContentPublicationRecordMode, contentstore.LegacyAuthorityFence, time.Time) error
}

type AutomationContentMigrationOptions struct {
	Worker    automationContentMigrationWorker
	Exporter  AutomationContentMigrationExporter
	Mappings  AutomationContentMigrationMappingStore
	Content   AutomationContentMigrationVersionStore
	Authority AutomationContentAuthorityActivator
	Cutover   AutomationContentCutoverStore
}

type AutomationContentMigrationRequest struct {
	RepositoryID             string
	CommitSHA                string
	Ref                      string
	ExpectedContentVersionID string
}

type AutomationContentMigrationResult struct {
	ContentID        string
	ContentVersionID string
	RootDigest       string
	FileCount        int
	SizeBytes        int64
	Replayed         bool
}

func (service *AutomationsService) ConfigureContentMigration(options AutomationContentMigrationOptions) {
	if service == nil {
		return
	}
	service.contentMigration = options.Worker
	service.migrationExporter = options.Exporter
	service.migrationMappings = options.Mappings
	service.migrationContent = options.Content
	service.contentAuthority = options.Authority
	service.contentCutover = options.Cutover
}

// RunExactAutomationContentMigration imports one caller-selected AGS commit,
// and records its exact Automation projection. It deliberately leaves legacy
// authority unchanged: deployment performs a bounded full pass and one later
// delta observation before calling ActivateNativeAutomationContent.
func (service *AutomationsService) RunExactAutomationContentMigration(ctx context.Context, workspaceID, subject, owner, repository string, request AutomationContentMigrationRequest) (AutomationContentMigrationResult, error) {
	if service == nil || service.contentMigration == nil || service.migrationExporter == nil || service.contentAuthority == nil || service.contentCutover == nil || service.repositoryStore == nil || service.store == nil {
		return AutomationContentMigrationResult{}, invalidArgument("Automation content migration is not configured")
	}
	service.contentCutoverMu.Lock()
	defer service.contentCutoverMu.Unlock()
	workspaceID = strings.TrimSpace(workspaceID)
	subject = strings.TrimSpace(subject)
	owner = strings.TrimSpace(owner)
	repository = strings.TrimSpace(repository)
	request.RepositoryID = strings.TrimSpace(request.RepositoryID)
	request.CommitSHA = strings.ToLower(strings.TrimSpace(request.CommitSHA))
	request.Ref = strings.TrimSpace(request.Ref)
	request.ExpectedContentVersionID = strings.TrimSpace(request.ExpectedContentVersionID)
	if err := service.validateScope(workspaceID, subject); err != nil {
		return AutomationContentMigrationResult{}, err
	}
	if err := validateAutomationRepository(owner, repository); err != nil || request.RepositoryID == "" || !automationCommitSHA.MatchString(request.CommitSHA) {
		return AutomationContentMigrationResult{}, invalidArgument("Automation content migration exact target is invalid")
	}
	if err := service.automationAuthorizer().CanWriteAutomationRepo(ctx, service.automationAuthorizationRequest(ctx, workspaceID, subject, AutomationAuthorizationRequest{RepositoryOwner: owner, RepositoryName: repository})); err != nil {
		return AutomationContentMigrationResult{}, err
	}
	bindingKey := automationRepositoryBindingKey(owner, repository)
	repo, err := service.repositoryStore.Get(ctx, workspaceID, resourcerepostore.ResourceTypeAutomationRepository, resourcerepostore.AutomationRepositoryResourceID(bindingKey), resourcerepostore.RepoRolePrimary)
	if err != nil || strings.TrimSpace(repo.AGSRepoID) != request.RepositoryID || !strings.EqualFold(strings.TrimSpace(repo.RepoFullName), bindingKey) {
		return AutomationContentMigrationResult{}, invalidArgument("Automation migration repository identity does not match primary binding")
	}
	authority, found, err := service.contentAuthority.GetAuthority(ctx, workspaceID, "automation", bindingKey)
	if err != nil {
		return AutomationContentMigrationResult{}, fmt.Errorf("resolve Automation migration authority: %w", err)
	}
	if found && authority.State != contentstore.AuthorityStateLegacyActive && authority.State != contentstore.AuthorityStateNativeActive {
		return AutomationContentMigrationResult{}, invalidArgument("Automation content migration authority state is invalid")
	}
	if found && authority.State == contentstore.AuthorityStateNativeActive {
		return service.replayActivatedAutomationContentMigration(ctx, workspaceID, owner, repository, request)
	}
	if found && authority.State == contentstore.AuthorityStateLegacyActive && authority.Generation != 0 {
		return AutomationContentMigrationResult{}, invalidArgument("Automation content migration legacy generation is unsupported")
	}
	fence, err := service.acquireAutomationContentFence(ctx, workspaceID, bindingKey, subject)
	if err != nil {
		return AutomationContentMigrationResult{}, err
	}
	defer service.releaseAutomationContentFence(ctx, fence)
	limits := contentpayload.DefaultLimits
	limits.MaxFiles = min(limits.MaxFiles, contentmigrationservice.MigrationMaxFiles)
	limits.MaxFileBytes = min(limits.MaxFileBytes, contentmigrationservice.MigrationMaxFileBytes)
	limits.MaxTotalBytes = min(limits.MaxTotalBytes, contentmigrationservice.MigrationMaxTotalBytes)
	result, err := service.contentMigration.RunExact(ctx, contentmigrationservice.Request{
		WorkspaceID: workspaceID, BindingKey: bindingKey, SourceCursor: request.CommitSHA,
		RepositoryID: request.RepositoryID, CommitSHA: request.CommitSHA,
		ExpectedHeadVersionID: request.ExpectedContentVersionID, Actor: subject, Limits: limits,
		AuthorityFence: &fence,
	}, func(exportCtx context.Context, exportLimits contentpayload.Limits) (contentmigrationservice.Export, error) {
		exported, exportErr := service.migrationExporter.ExportExactAutomationRepository(exportCtx, bindingKey, request.CommitSHA, exportLimits)
		if exportErr != nil {
			return contentmigrationservice.Export{}, exportErr
		}
		if validationErr := service.validateAutomationMigrationExport(exported); validationErr != nil {
			return contentmigrationservice.Export{}, validationErr
		}
		return exported, nil
	}, func(projectCtx context.Context, migration contentmigrationservice.Result) error {
		currentAuthority, currentFound, authorityErr := service.contentAuthority.GetAuthority(projectCtx, workspaceID, "automation", bindingKey)
		if authorityErr != nil {
			return authorityErr
		}
		if currentFound && currentAuthority.State == contentstore.AuthorityStateNativeActive {
			current, currentErr := service.store.GetCurrentContentRepository(projectCtx, workspaceID, owner, repository)
			if currentErr != nil || current.ContentID != migration.ContentID || current.ContentVersionID != migration.VersionID || currentAuthority.Generation != 1 {
				return contentstore.ErrAuthorityConflict
			}
			return nil
		}
		if currentFound && (currentAuthority.State != contentstore.AuthorityStateLegacyActive || currentAuthority.Generation != 0) {
			return contentstore.ErrAuthorityConflict
		}
		definitions := make([]automationstore.AutomationContentDefinition, 0)
		changedPaths := make([]string, 0)
		for _, path := range migration.Paths {
			if !isAutomationSourcePath(path.Path) {
				continue
			}
			definitions = append(definitions, automationstore.AutomationContentDefinition{
				WorkspaceID: workspaceID, ContentID: migration.ContentID, ContentVersionID: migration.VersionID,
				DefinitionPath: path.Path, Digest: path.Digest, SizeBytes: path.SizeBytes, CreatedAt: time.Now().UTC(),
			})
			changedPaths = append(changedPaths, path.Path)
		}
		if len(definitions) == 0 || len(definitions) > automationstore.MaxAutomationDefinitionsPerVersion {
			return invalidArgument("Automation migration definition projection is invalid")
		}
		sort.Strings(changedPaths)
		changedJSON, _ := json.Marshal(changedPaths)
		publishedAt := service.now().UTC()
		if err := service.contentCutover.RecordLegacyContentPublication(projectCtx, automationstore.AutomationContentPublication{
			WorkspaceID: workspaceID, ContentID: migration.ContentID, ContentVersionID: migration.VersionID,
			RepositoryOwner: owner, RepositoryName: repository, Ref: request.Ref,
			ChangedPathsJSON: string(changedJSON), ActorID: subject, CreatedAt: publishedAt,
		}, definitions, request.ExpectedContentVersionID, automationstore.ContentPublicationRecordStrict, fence, publishedAt); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return AutomationContentMigrationResult{}, err
	}
	return AutomationContentMigrationResult{
		ContentID: result.ContentID, ContentVersionID: result.VersionID, RootDigest: result.RootDigest,
		FileCount: result.FileCount, SizeBytes: result.SizeBytes, Replayed: result.Replayed,
	}, nil
}

func (service *AutomationsService) replayActivatedAutomationContentMigration(ctx context.Context, workspaceID, owner, repository string, request AutomationContentMigrationRequest) (AutomationContentMigrationResult, error) {
	if service.migrationMappings == nil {
		return AutomationContentMigrationResult{}, invalidArgument("Automation content migration mapping store is not configured")
	}
	mapping, err := service.migrationMappings.GetMapping(ctx, request.RepositoryID, request.CommitSHA)
	if err != nil || !automationContentActivationMappingMatches(mapping, workspaceID, request.RepositoryID, request.CommitSHA, mapping.ContentVersionID) {
		return AutomationContentMigrationResult{}, contentstore.ErrAuthorityConflict
	}
	current, err := service.store.GetCurrentContentRepository(ctx, workspaceID, owner, repository)
	if err != nil || current.ContentID != mapping.ContentID || current.ContentVersionID != mapping.ContentVersionID {
		return AutomationContentMigrationResult{}, contentstore.ErrAuthorityConflict
	}
	publication, err := service.store.GetContentPublication(ctx, workspaceID, mapping.ContentID, mapping.ContentVersionID)
	if err != nil || publication.RepositoryOwner != owner || publication.RepositoryName != repository {
		return AutomationContentMigrationResult{}, contentstore.ErrAuthorityConflict
	}
	return AutomationContentMigrationResult{
		ContentID: mapping.ContentID, ContentVersionID: mapping.ContentVersionID, RootDigest: mapping.RootDigest,
		FileCount: mapping.FileCount, SizeBytes: mapping.SizeBytes, Replayed: true,
	}, nil
}

func (service *AutomationsService) validateAutomationMigrationExport(exported contentmigrationservice.Export) error {
	definitions := 0
	maxSourceBytes := service.automationLimits().MaxAutomationSourceBytes
	for _, file := range exported.Files {
		if !isAutomationSourcePath(file.Path) {
			continue
		}
		definitions++
		if len(file.Data) > maxSourceBytes {
			return invalidArgument("Automation migration definition exceeds source limit")
		}
		_, report := service.decodeAndValidateAutomationContent(string(file.Data), file.Path)
		if !report.Valid {
			return invalidArgument("Automation migration definition is invalid")
		}
	}
	if definitions == 0 || definitions > automationstore.MaxAutomationDefinitionsPerVersion {
		return invalidArgument("Automation migration definition projection is invalid")
	}
	return nil
}

var automationCommitSHA = regexp.MustCompile(`^[0-9a-f]{40}$`)

type CheckoutAutomationContentMigrationExporter struct {
	Checkout runtime.CheckoutMaterializer
	WorkRoot string
}

func (exporter CheckoutAutomationContentMigrationExporter) ExportExactAutomationRepository(ctx context.Context, repositoryFullName, commitSHA string, limits contentpayload.Limits) (contentmigrationservice.Export, error) {
	if exporter.Checkout == nil || !automationCommitSHA.MatchString(strings.ToLower(strings.TrimSpace(commitSHA))) {
		return contentmigrationservice.Export{}, fmt.Errorf("Automation migration exporter is not configured")
	}
	parent, err := os.MkdirTemp(exporter.WorkRoot, "automation-migration-")
	if err != nil {
		return contentmigrationservice.Export{}, err
	}
	defer os.RemoveAll(parent)
	root := filepath.Join(parent, "tree")
	if _, err := exporter.Checkout.Materialize(ctx, runtime.CheckoutRequest{
		RepositoryFullName: repositoryFullName, CommitSHA: commitSHA, Destination: root, Clean: true,
		MaxFiles: limits.MaxFiles, MaxBytes: limits.MaxTotalBytes,
	}); err != nil {
		return contentmigrationservice.Export{}, err
	}
	files, _, err := collectAutomationContentWorkspace(root, limits, "", "")
	if err != nil {
		return contentmigrationservice.Export{}, err
	}
	result := make([]contentmigrationservice.File, 0, len(files))
	for _, file := range files {
		data, readErr := os.ReadFile(filepath.Join(root, filepath.FromSlash(file.Path)))
		if readErr != nil {
			return contentmigrationservice.Export{}, readErr
		}
		result = append(result, contentmigrationservice.File{Path: file.Path, Data: data})
	}
	return contentmigrationservice.Export{Files: result}, nil
}

var _ AutomationContentMigrationExporter = CheckoutAutomationContentMigrationExporter{}
