package skills

import (
	"context"
	"math"
	"strings"
	"time"

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

type SkillRepositoryBackup struct {
	TagName   string
	Name      string
	CommitSHA string
	VersionID string
	CreatedAt time.Time
}

type SkillRepositoryBackupPage struct {
	Items   []SkillRepositoryBackup
	Page    int
	PerPage int
	HasMore bool
}

type SkillRepositoryBackupCreateMutation struct {
	Name  string
	Actor repos.ResourceRepositoryActor
}

type skillRepositoryBackupTarget struct {
	Skill      store.Skill
	Repository repos.RepositoryIdentity
	Branch     string
	FullName   string
}

func (s *Service) ListSkillRepositoryBackups(ctx context.Context, workspaceID, subject, skillID, query string, page, perPage int) (SkillRepositoryBackupPage, error) {
	if page > 0 && perPage > 0 {
		if _, ok := checkedSkillPageOffset(page, perPage, 0, math.MaxInt); !ok {
			return SkillRepositoryBackupPage{}, invalidArgument("skill backup page is out of bounds")
		}
	}
	skill, err := s.skillForBackup(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillRepositoryBackupPage{}, err
	}
	native, err := s.nativeSkillRead(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return SkillRepositoryBackupPage{}, err
	}
	if native {
		offset, ok := checkedSkillPageOffset(page, perPage, 100, 255)
		if !ok {
			return SkillRepositoryBackupPage{}, invalidArgument("skill backup page is out of bounds")
		}
		return s.listNativeSkillBackups(ctx, skill, subject, query, page, perPage, offset)
	}
	target, err := s.skillRepositoryBackupTarget(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillRepositoryBackupPage{}, err
	}
	if s.repositoryBackups == nil {
		return SkillRepositoryBackupPage{}, invalidArgument("skill repository backups are not configured")
	}
	result, err := s.repositoryBackups.ListMemoryRepositoryBackups(ctx, memory.MemoryRepositoryBackupListRequest{
		Repository: target.Repository,
		Query:      query,
		Page:       page,
		PerPage:    perPage,
	})
	if err != nil {
		return SkillRepositoryBackupPage{}, err
	}
	return skillRepositoryBackupPageFromMemory(result), nil
}

func (s *Service) CreateSkillRepositoryBackup(ctx context.Context, workspaceID, subject, skillID string, input SkillRepositoryBackupCreateMutation) (output SkillRepositoryBackup, err error) {
	skill, err := s.skillForBackup(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	writeRoute, err := s.beginSkillWrite(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	if writeRoute.native {
		pin, err := s.nativeContent.CreateBackup(ctx, nativeSkillRequest(skill.ID, skill.WorkspaceID, skill.ContentID, skill.ActiveContentVersionID, subject), firstNonEmpty(input.Name, "Skill backup"))
		if err != nil {
			return SkillRepositoryBackup{}, err
		}
		return SkillRepositoryBackup{TagName: pin.ID, Name: pin.Label, VersionID: pin.VersionID, CreatedAt: pin.CreatedAt}, nil
	}
	target, err := s.skillRepositoryBackupTarget(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	if s.repositoryBackups == nil {
		return SkillRepositoryBackup{}, invalidArgument("skill repository backups are not configured")
	}
	authorName, authorEmail := repos.RepositoryCommitAuthorFromActor(input.Actor, "MeshX Skills", "meshx-skills@agentteam.local")
	result, err := s.repositoryBackups.CreateMemoryRepositoryBackup(ctx, memory.MemoryRepositoryBackupCreateRequest{
		Repository:  target.Repository,
		Branch:      target.Branch,
		Name:        input.Name,
		Now:         s.now(),
		AuthorName:  authorName,
		AuthorEmail: authorEmail,
	})
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	return skillRepositoryBackupFromMemory(result), nil
}

func (s *Service) RestoreSkillRepositoryBackup(ctx context.Context, workspaceID, subject, skillID, tagName string) (output SkillRepositoryBackup, err error) {
	skill, err := s.skillForBackup(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	writeRoute, err := s.beginSkillWrite(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	if writeRoute.native {
		return s.restoreNativeSkillBackup(ctx, skill, subject, tagName)
	}
	target, err := s.skillRepositoryBackupTarget(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	if s.repositoryBackups == nil {
		return SkillRepositoryBackup{}, invalidArgument("skill repository backups are not configured")
	}
	result, err := s.repositoryBackups.RestoreMemoryRepositoryBackup(ctx, memory.MemoryRepositoryBackupRestoreRequest{
		Repository: target.Repository,
		Branch:     target.Branch,
		TagName:    tagName,
		Now:        s.now(),
	})
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	if err := s.refreshSkillCountAfterBackupRestore(ctx, strings.TrimSpace(subject), target); err != nil {
		return SkillRepositoryBackup{}, err
	}
	return skillRepositoryBackupFromMemory(result.Restored), nil
}

func (s *Service) skillForBackup(ctx context.Context, workspaceID, subject, skillID string) (store.Skill, error) {
	if s == nil || s.store == nil {
		return store.Skill{}, invalidArgument("skill service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return store.Skill{}, err
	}
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return store.Skill{}, err
	}
	if !skillRoleCanManage(skill.Role) {
		return store.Skill{}, forbidden("skill requires manage access")
	}
	return skill, nil
}

func (s *Service) listNativeSkillBackups(ctx context.Context, skill store.Skill, subject, query string, page, perPage, offset int) (SkillRepositoryBackupPage, error) {
	pins, err := s.nativeContent.ListBackups(ctx, nativeSkillRequest(skill.ID, skill.WorkspaceID, skill.ContentID, skill.ActiveContentVersionID, subject), 256)
	if err != nil {
		return SkillRepositoryBackupPage{}, err
	}
	query = strings.ToLower(strings.TrimSpace(query))
	items := make([]SkillRepositoryBackup, 0, min(len(pins), 256))
	for _, pin := range pins {
		if query != "" && !strings.Contains(strings.ToLower(pin.Label), query) {
			continue
		}
		items = append(items, SkillRepositoryBackup{TagName: pin.ID, Name: pin.Label, VersionID: pin.VersionID, CreatedAt: pin.CreatedAt})
	}
	if offset > len(items) {
		offset = len(items)
	}
	pageSize := min(perPage, len(items)-offset)
	end := offset + pageSize
	return SkillRepositoryBackupPage{Items: items[offset:end], Page: page, PerPage: perPage, HasMore: end < len(items)}, nil
}

func (s *Service) restoreNativeSkillBackup(ctx context.Context, skill store.Skill, subject, tagName string) (SkillRepositoryBackup, error) {
	pins, err := s.nativeContent.ListBackups(ctx, nativeSkillRequest(skill.ID, skill.WorkspaceID, skill.ContentID, skill.ActiveContentVersionID, subject), 256)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	var targetVersionID, label, pinID string
	var pinCreatedAt time.Time
	for _, pin := range pins {
		if pin.ID == strings.TrimSpace(tagName) {
			targetVersionID, label, pinID, pinCreatedAt = pin.VersionID, pin.Label, pin.ID, pin.CreatedAt
			break
		}
	}
	if targetVersionID == "" {
		return SkillRepositoryBackup{}, notFound("skill backup not found")
	}
	result, err := s.nativeContent.Restore(ctx, nativeSkillRequest(skill.ID, skill.WorkspaceID, skill.ContentID, skill.ActiveContentVersionID, subject), targetVersionID)
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	beforeCount := skill.SkillCount
	_, _, err = s.finalizeNativeSkillVersion(ctx, skill, subject, result, skillActivityBackupRestored, "backup", pinID, map[string]any{
		"versionId": result.VersionID, "skillCount": result.SkillCount, "previousCount": beforeCount,
	}, "")
	if err != nil {
		return SkillRepositoryBackup{}, err
	}
	return SkillRepositoryBackup{TagName: pinID, Name: label, VersionID: result.VersionID, CreatedAt: pinCreatedAt}, nil
}

func (s *Service) skillRepositoryBackupTarget(ctx context.Context, workspaceID, subject, skillID string) (skillRepositoryBackupTarget, error) {
	if s == nil || s.store == nil {
		return skillRepositoryBackupTarget{}, invalidArgument("skill service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return skillRepositoryBackupTarget{}, err
	}
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return skillRepositoryBackupTarget{}, err
	}
	if !skillRoleCanManage(skill.Role) {
		return skillRepositoryBackupTarget{}, forbidden("skill requires manage access")
	}
	repositories, err := s.GetSkillRepositories(ctx, workspaceID, subject, skill.ID)
	if err != nil {
		return skillRepositoryBackupTarget{}, err
	}
	if repositories.Primary == nil {
		return skillRepositoryBackupTarget{}, invalidArgument("skill primary repository is not configured")
	}
	repo := repos.IdentityFromStore(*repositories.Primary)
	if strings.TrimSpace(repo.FullName) == "" || strings.TrimSpace(repo.Owner) == "" || strings.TrimSpace(repo.Name) == "" {
		return skillRepositoryBackupTarget{}, invalidArgument("skill primary repository is not configured")
	}
	branch := firstNonEmpty(repositories.Primary.DefaultRef, repo.DefaultBranch, "main")
	repo.DefaultBranch = branch
	return skillRepositoryBackupTarget{
		Skill:      skill,
		Repository: repo,
		Branch:     branch,
		FullName:   firstNonEmpty(repo.FullName, repo.Owner+"/"+repo.Name),
	}, nil
}

func (s *Service) refreshSkillCountAfterBackupRestore(ctx context.Context, subject string, target skillRepositoryBackupTarget) error {
	if s == nil || s.store == nil {
		return invalidArgument("skill service is not configured")
	}
	if s.repositoryContent == nil {
		return invalidArgument("skill repository content manager is not configured")
	}
	repo := repos.RepositoryIdentity{
		ID:            target.Repository.ID,
		Owner:         target.Repository.Owner,
		Name:          target.Repository.Name,
		FullName:      target.Repository.FullName,
		DefaultBranch: target.Branch,
	}
	preview, err := s.repositoryContent.ListSkillRepositorySkills(ctx, repo)
	if err != nil {
		return err
	}
	updated := target.Skill
	updated.SkillCount = validSkillRepositoryCount(preview.DetectedSkills)
	updated.UpdatedBy = subject
	updated.UpdatedAt = s.now()
	err = s.store.Transaction(ctx, func(tx *store.Store) error {
		if _, err := tx.UpdateSkill(ctx, updated); err != nil {
			return err
		}
		return writeSkillActivity(ctx, s.skillActivityStore(tx), updated, target.FullName, skillActivityBackupRestored, subject, "backup", target.Repository.FullName, map[string]any{
			"skillCount":    updated.SkillCount,
			"previousCount": target.Skill.SkillCount,
		}, updated.UpdatedAt)
	})
	return mapStoreError(err)
}

func validSkillRepositoryCount(rows []SkillRepositoryDetectedSkill) int {
	count := 0
	for _, row := range rows {
		if strings.TrimSpace(row.Status) == SkillRepositorySkillStatusValid {
			count++
		}
	}
	return count
}

func skillRepositoryBackupFromMemory(item memory.AgentCloudMemoryBackup) SkillRepositoryBackup {
	return SkillRepositoryBackup{
		TagName:   item.TagName,
		Name:      item.Name,
		CommitSHA: item.CommitSHA,
		CreatedAt: item.CreatedAt,
	}
}

func skillRepositoryBackupPageFromMemory(page memory.AgentCloudMemoryBackupPage) SkillRepositoryBackupPage {
	items := make([]SkillRepositoryBackup, 0, len(page.Items))
	for _, item := range page.Items {
		items = append(items, skillRepositoryBackupFromMemory(item))
	}
	return SkillRepositoryBackupPage{Items: items, Page: page.Page, PerPage: page.PerPage, HasMore: page.HasMore}
}
