package memory

import (
	"context"
	"encoding/json"
	"errors"
	"strings"

	"gorm.io/gorm"

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

type AgentCloudMemoryCommitListOptions struct {
	Ref     string
	Path    string
	Author  string
	Since   string
	Until   string
	Page    int
	PerPage int
	Actor   repos.ResourceRepositoryActor
}

type AgentCloudMemoryCompareOptions struct {
	Base  string
	Head  string
	Actor repos.ResourceRepositoryActor
}

func (s *MemoryService) ListAgentCloudMemoryCommits(ctx context.Context, workspaceID, subject, agentID string, opts AgentCloudMemoryCommitListOptions) (json.RawMessage, error) {
	target, err := s.agentCloudMemoryRepository(ctx, workspaceID, subject, agentID)
	if err != nil {
		return nil, err
	}
	if err := s.repairAgentCloudMemoryRepositoryAccess(ctx, target, strings.TrimSpace(subject), opts.Actor); err != nil {
		return nil, err
	}
	if s.repositoryHistory == nil {
		return nil, invalidArgument("memory repository history is not configured")
	}
	return s.repositoryHistory.ListMemoryRepositoryCommits(ctx, MemoryRepositoryCommitListRequest{
		Repository: target.Repo,
		Ref:        firstNonEmptyString(opts.Ref, target.Branch, target.Repo.DefaultBranch, "main"),
		Path:       opts.Path,
		Author:     opts.Author,
		Since:      opts.Since,
		Until:      opts.Until,
		Page:       opts.Page,
		PerPage:    opts.PerPage,
	})
}

func (s *MemoryService) GetAgentCloudMemoryCompare(ctx context.Context, workspaceID, subject, agentID string, opts AgentCloudMemoryCompareOptions) (json.RawMessage, error) {
	target, err := s.agentCloudMemoryRepository(ctx, workspaceID, subject, agentID)
	if err != nil {
		return nil, err
	}
	if err := s.repairAgentCloudMemoryRepositoryAccess(ctx, target, strings.TrimSpace(subject), opts.Actor); err != nil {
		return nil, err
	}
	if s.repositoryHistory == nil {
		return nil, invalidArgument("memory repository history is not configured")
	}
	base := strings.TrimSpace(opts.Base)
	head := strings.TrimSpace(opts.Head)
	if base == "" || head == "" {
		return nil, invalidArgument("base and head are required")
	}
	return s.repositoryHistory.GetMemoryRepositoryCompare(ctx, MemoryRepositoryCompareRequest{
		Repository: target.Repo,
		Base:       base,
		Head:       head,
	})
}

func (s *MemoryService) repairAgentCloudMemoryRepositoryAccess(ctx context.Context, target agentCloudMemoryRepository, subject string, actor repos.ResourceRepositoryActor) error {
	if s.repositoryStore == nil {
		return nil
	}
	subject = strings.TrimSpace(subject)
	if subject == "" {
		return nil
	}
	intentID, intentProfile, err := s.memoryRepositoryAccessIntent(ctx, target, subject)
	if err != nil {
		return err
	}
	if intentID == "" {
		return nil
	}
	repo := target.ResourceRepository
	rowID := reposstore.ResourceRepositoryAccessGrantID(repo.WorkspaceID, repo.ResourceType, repo.ResourceID, repo.RepoRole, "user", subject, reposstore.AccessIntentTypeMemoryGrant, intentID)
	current, err := s.repositoryStore.GetAccessGrant(ctx, repo.WorkspaceID, rowID)
	if err == nil && isCurrentMaterializedMemoryAccessGrant(current, repo.RepoFullName) {
		return nil
	}
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}

	now := s.now()
	status := reposstore.AccessStatusCurrent
	lastError := ""
	materialized := false
	allowExistingDirectUpdate := err == nil && current.Materialized
	if s.repositoryGrantor == nil {
		return invalidArgument("memory repository access grantor is not configured")
	}
	result, grantErr := s.repositoryGrantor.GrantMemoryRepositoryAccess(ctx, MemoryRepositoryAccessGrantRequest{
		Repository:                target.Repo,
		Actor:                     actor,
		Permission:                reposstore.AccessPermissionRead,
		AllowExistingDirectUpdate: allowExistingDirectUpdate,
	})
	if grantErr != nil {
		status = reposstore.AccessStatusBlocked
		lastError = strings.TrimSpace(grantErr.Error())
		materialized = err == nil && current.Materialized
	} else {
		materialized = result.Materialized
	}
	_, err = s.repositoryStore.UpsertAccessGrant(ctx, reposstore.ResourceRepositoryAccessGrant{
		WorkspaceID:      repo.WorkspaceID,
		ResourceType:     repo.ResourceType,
		ResourceID:       repo.ResourceID,
		RepoRole:         repo.RepoRole,
		RepoFullName:     repo.RepoFullName,
		AGSRepoID:        repo.AGSRepoID,
		SubjectType:      "user",
		SubjectID:        subject,
		Permission:       reposstore.AccessPermissionRead,
		IntentType:       reposstore.AccessIntentTypeMemoryGrant,
		IntentID:         intentID,
		IntentProfile:    intentProfile,
		Materialized:     materialized,
		Status:           status,
		LastError:        lastError,
		CreatedBy:        subject,
		UpdatedBy:        subject,
		ReconciledBy:     subject,
		CreatedAt:        now,
		UpdatedAt:        now,
		LastReconciledAt: &now,
	})
	if err != nil {
		return err
	}
	if grantErr != nil {
		return grantErr
	}
	return nil
}

func (s *MemoryService) memoryRepositoryAccessIntent(ctx context.Context, target agentCloudMemoryRepository, subject string) (string, string, error) {
	grant, err := s.store.GetMemoryGrant(ctx, target.Workspace, target.Memory.ID, "user", subject)
	if err == nil {
		return strings.TrimSpace(grant.ID), normalizeMemoryGrantRole(grant.Role, grant.SubjectType), nil
	}
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return "", "", err
	}
	if strings.TrimSpace(target.Memory.CreatedBy) == subject {
		return target.Memory.ID, "owner", nil
	}
	return "", "", forbidden("memory requires manager access")
}

func isCurrentMaterializedMemoryAccessGrant(row reposstore.ResourceRepositoryAccessGrant, repoFullName string) bool {
	if strings.TrimSpace(row.Status) != reposstore.AccessStatusCurrent || !row.Materialized {
		return false
	}
	if strings.TrimSpace(row.RepoFullName) != strings.TrimSpace(repoFullName) {
		return false
	}
	return memoryRepositoryAccessPermissionRank(row.Permission) >= memoryRepositoryAccessPermissionRank(reposstore.AccessPermissionRead)
}

func memoryRepositoryAccessPermissionRank(permission string) int {
	switch strings.TrimSpace(permission) {
	case reposstore.AccessPermissionAdmin:
		return 3
	case reposstore.AccessPermissionWrite:
		return 2
	case reposstore.AccessPermissionRead:
		return 1
	default:
		return 0
	}
}
