package provision

import (
	"context"
	"errors"
	"fmt"

	"sub2api-cn-relay-manager/internal/host/sub2api"
	"sub2api-cn-relay-manager/internal/pack"
	"sub2api-cn-relay-manager/internal/store/sqlite"
)

type rollbackHost interface {
	ListManagedResources(ctx context.Context, req sub2api.ListManagedResourcesRequest) (sub2api.ManagedResourceSnapshot, error)
	DeleteAccount(ctx context.Context, accountID string) error
	DeletePlan(ctx context.Context, planID string) error
	DeleteChannel(ctx context.Context, channelID string) error
	DeleteGroup(ctx context.Context, groupID string) error
}

type RollbackRequest struct {
	Provider pack.ProviderManifest
}

type RollbackReport struct {
	AccountsDeleted int
	PlansDeleted    int
	ChannelsDeleted int
	GroupsDeleted   int
}

type RollbackService struct {
	host rollbackHost
}

func NewRollbackService(host rollbackHost) *RollbackService {
	return &RollbackService{host: host}
}

func (s *RollbackService) Rollback(ctx context.Context, req RollbackRequest) (RollbackReport, error) {
	if s.host == nil {
		return RollbackReport{}, fmt.Errorf("rollback host is required")
	}

	names := SuggestResourceNames(req.Provider)
	snapshot, err := s.host.ListManagedResources(ctx, sub2api.ListManagedResourcesRequest{
		GroupName:         names.Group,
		ChannelName:       names.Channel,
		PlanName:          names.Plan,
		AccountNamePrefix: SuggestAccountNamePrefix(req.Provider),
	})
	if err != nil {
		return RollbackReport{}, fmt.Errorf("list managed resources: %w", err)
	}
	return s.rollbackNamedResources(ctx, snapshot)
}

func (s *RollbackService) RollbackStoredResources(ctx context.Context, resources []sqlite.ManagedResource) (RollbackReport, error) {
	if s.host == nil {
		return RollbackReport{}, fmt.Errorf("rollback host is required")
	}
	return s.rollbackNamedResources(ctx, namedResourceSnapshotFromStored(resources))
}

func (s *RollbackService) rollbackNamedResources(ctx context.Context, snapshot sub2api.ManagedResourceSnapshot) (RollbackReport, error) {
	var report RollbackReport
	var errs []error
	for index := len(snapshot.Accounts) - 1; index >= 0; index-- {
		if err := s.host.DeleteAccount(ctx, snapshot.Accounts[index].ID); err != nil {
			errs = append(errs, fmt.Errorf("delete account %s: %w", snapshot.Accounts[index].ID, err))
			continue
		}
		report.AccountsDeleted++
	}
	for index := len(snapshot.Plans) - 1; index >= 0; index-- {
		if err := s.host.DeletePlan(ctx, snapshot.Plans[index].ID); err != nil {
			errs = append(errs, fmt.Errorf("delete plan %s: %w", snapshot.Plans[index].ID, err))
			continue
		}
		report.PlansDeleted++
	}
	for index := len(snapshot.Channels) - 1; index >= 0; index-- {
		if err := s.host.DeleteChannel(ctx, snapshot.Channels[index].ID); err != nil {
			errs = append(errs, fmt.Errorf("delete channel %s: %w", snapshot.Channels[index].ID, err))
			continue
		}
		report.ChannelsDeleted++
	}
	for index := len(snapshot.Groups) - 1; index >= 0; index-- {
		if err := s.host.DeleteGroup(ctx, snapshot.Groups[index].ID); err != nil {
			errs = append(errs, fmt.Errorf("delete group %s: %w", snapshot.Groups[index].ID, err))
			continue
		}
		report.GroupsDeleted++
	}

	if len(errs) > 0 {
		return report, errors.Join(errs...)
	}
	return report, nil
}

func namedResourceSnapshotFromStored(resources []sqlite.ManagedResource) sub2api.ManagedResourceSnapshot {
	snapshot := sub2api.ManagedResourceSnapshot{}
	for _, resource := range resources {
		ref := sub2api.NamedResource{ID: resource.HostResourceID, Name: resource.ResourceName}
		switch resource.ResourceType {
		case "account":
			snapshot.Accounts = append(snapshot.Accounts, ref)
		case "plan":
			snapshot.Plans = append(snapshot.Plans, ref)
		case "channel":
			snapshot.Channels = append(snapshot.Channels, ref)
		case "group":
			snapshot.Groups = append(snapshot.Groups, ref)
		}
	}
	return snapshot
}
