package teamworkflow

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	serviceteamworkflow "github.com/tidbcloud/AgentTeam/internal/service/teamworkflow"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/team"
	storeteamworkflow "github.com/tidbcloud/AgentTeam/internal/store/teamworkflow"
	"github.com/tidbcloud/AgentTeam/internal/store/workflow/plan"
	"github.com/tidbcloud/AgentTeam/internal/workflow/planmanage"
)

const defaultPageLimit = 50

type teamReader interface {
	GetTeamForUser(context.Context, string, string, string) (team.TeamView, error)
}

type relationReader interface {
	List(context.Context, storeteamworkflow.ListParams) ([]storeteamworkflow.View, error)
	Load(context.Context, string, string, string, bool) (storeteamworkflow.View, error)
}

type relationLifecycle interface {
	SetAvailability(context.Context, serviceteamworkflow.LifecycleInput) (serviceteamworkflow.LifecycleResult, error)
	Delete(context.Context, serviceteamworkflow.DeleteInput) (serviceteamworkflow.DeleteResult, error)
}

// StoreService is the production browser adapter. The Team store remains the
// creator-private authorization authority; relation reads and lifecycle
// deletion then stay constrained to that already-authorized nested Team.
type StoreService struct {
	teams     teamReader
	relations relationReader
	lifecycle relationLifecycle
}

func NewStoreService(db coredb.DB, lifecycle *serviceteamworkflow.Service) *StoreService {
	return &StoreService{
		teams: team.New(db), relations: storeteamworkflow.New(db), lifecycle: lifecycle,
	}
}

func (service *StoreService) validate() error {
	if service == nil || service.teams == nil || service.relations == nil || service.lifecycle == nil {
		return fmt.Errorf("Team Workflow browser service is not configured")
	}
	return nil
}

type pageCursor struct {
	UpdatedAt time.Time `json:"updatedAt"`
	ID        string    `json:"id"`
}

func (service *StoreService) ListForUser(ctx context.Context, input ListInput) (Page, error) {
	if err := service.validate(); err != nil {
		return Page{}, err
	}
	if _, err := service.teams.GetTeamForUser(ctx, input.UserID, input.WorkspaceID, input.TeamID); err != nil {
		return Page{}, err
	}
	cursor, err := decodePageCursor(input.Cursor)
	if err != nil {
		return Page{}, err
	}
	limit := input.Limit
	if limit <= 0 {
		limit = defaultPageLimit
	}
	var beforeUpdatedAt *time.Time
	if !cursor.UpdatedAt.IsZero() {
		value := cursor.UpdatedAt.UTC()
		beforeUpdatedAt = &value
	}
	views, err := service.relations.List(ctx, storeteamworkflow.ListParams{
		WorkspaceID: strings.TrimSpace(input.WorkspaceID), TeamID: strings.TrimSpace(input.TeamID),
		State: strings.TrimSpace(input.State), Search: strings.TrimSpace(input.Search), Limit: limit + 1,
		BeforeUpdatedAt: beforeUpdatedAt, BeforeID: cursor.ID,
	})
	if err != nil {
		return Page{}, err
	}
	hasMore := len(views) > limit
	if hasMore {
		views = views[:limit]
	}
	page := Page{Items: make([]Item, 0, len(views))}
	for _, view := range views {
		page.Items = append(page.Items, itemFromStore(view))
	}
	if hasMore {
		last := views[len(views)-1].TeamWorkflow
		page.NextCursor, err = encodePageCursor(pageCursor{UpdatedAt: last.UpdatedAt, ID: last.ID})
		if err != nil {
			return Page{}, err
		}
	}
	return page, nil
}

func (service *StoreService) GetForUser(ctx context.Context, input GetInput) (Item, error) {
	if err := service.validate(); err != nil {
		return Item{}, err
	}
	if _, err := service.teams.GetTeamForUser(ctx, input.UserID, input.WorkspaceID, input.TeamID); err != nil {
		return Item{}, err
	}
	view, err := service.relations.Load(ctx, input.WorkspaceID, input.TeamID, input.TeamWorkflowID, false)
	if err != nil {
		return Item{}, err
	}
	return itemFromStore(view), nil
}

func (service *StoreService) DeleteForUser(ctx context.Context, input DeleteInput) error {
	if err := service.validate(); err != nil {
		return err
	}
	team, err := service.teams.GetTeamForUser(ctx, input.UserID, input.WorkspaceID, input.TeamID)
	if err != nil {
		return err
	}
	view, err := service.relations.Load(ctx, input.WorkspaceID, input.TeamID, input.TeamWorkflowID, true)
	if err != nil {
		return err
	}
	if view.TeamWorkflow.DeletedAt != nil {
		return nil
	}
	_, err = service.lifecycle.Delete(ctx, serviceteamworkflow.DeleteInput{
		WorkspaceID: strings.TrimSpace(input.WorkspaceID), TeamID: strings.TrimSpace(input.TeamID),
		TeamWorkflowID: strings.TrimSpace(input.TeamWorkflowID),
		ActorMemberID:  team.Team.CreatedByMemberID, ExpectedRevision: input.ExpectedRevision,
		ClientRequestID: "browser-delete-v1:" + strings.TrimSpace(input.TeamWorkflowID),
	})
	return err
}

func (service *StoreService) SetAvailabilityForUser(ctx context.Context, input LifecycleInput) (Item, error) {
	if err := service.validate(); err != nil {
		return Item{}, err
	}
	team, err := service.teams.GetTeamForUser(ctx, input.UserID, input.WorkspaceID, input.TeamID)
	if err != nil {
		return Item{}, err
	}
	domainInput := serviceteamworkflow.LifecycleInput{
		WorkspaceID: strings.TrimSpace(input.WorkspaceID), TeamID: strings.TrimSpace(input.TeamID),
		TeamWorkflowID: strings.TrimSpace(input.TeamWorkflowID), ActorMemberID: team.Team.CreatedByMemberID,
		ExpectedRevision: input.ExpectedRevision, Archived: input.Archived,
	}
	_, err = service.lifecycle.SetAvailability(ctx, domainInput)
	if err != nil {
		return Item{}, err
	}
	view, err := service.relations.Load(ctx, input.WorkspaceID, input.TeamID, input.TeamWorkflowID, false)
	if err != nil {
		return Item{}, err
	}
	return itemFromStore(view), nil
}

func itemFromStore(view storeteamworkflow.View) Item {
	relation := view.TeamWorkflow
	authoringStatus := view.AuthoringStatus()
	item := Item{
		ID: relation.ID, TeamID: relation.TeamID, Revision: relation.Revision,
		Status: view.Status(), AuthoringStatus: authoringStatus, Availability: view.Availability(),
		Actions: Actions{
			CanOpen: view.Plan != nil,
			CanRetry: authoringStatus == storeteamworkflow.AuthoringStateGenerating ||
				authoringStatus == storeteamworkflow.AuthoringStateFailed,
			CanArchive: relation.DeletedAt == nil && relation.AuthoringState == storeteamworkflow.AuthoringStateIdle &&
				view.Plan != nil && view.Plan.ArchivedAt == nil,
			CanRestore: relation.DeletedAt == nil && authoringStatus == storeteamworkflow.AuthoringStateIdle &&
				view.Plan != nil && view.Plan.ArchivedAt != nil,
			CanDelete: relation.DeletedAt == nil,
		},
		CreatedAt: relation.CreatedAt, UpdatedAt: relation.UpdatedAt,
	}
	if view.Plan != nil {
		plan := planFromStore(*view.Plan)
		item.Plan = &plan
	}
	if relation.AuthoringState == storeteamworkflow.AuthoringStateFailed {
		item.Failure = &Failure{Code: relation.LastErrorCode, Message: relation.LastErrorMessage}
	}
	return item
}

func planFromStore(value plan.Plan) planmanage.Plan {
	plan := planmanage.Plan{
		ID: value.ID, WorkspaceID: value.WorkspaceID, Key: value.PlanKey,
		DisplayName: value.DisplayName, DescriptionMD: value.DescriptionMD,
		State: planmanage.PlanState(value.State), Revision: value.Revision,
		CreatedBy: actorFromStore(value.CreatedBy), CreatedAt: value.CreatedAt,
		UpdatedBy: actorFromStore(value.UpdatedBy), UpdatedAt: value.UpdatedAt,
		ArchivedAt: value.ArchivedAt,
	}
	if value.CurrentVersion != nil {
		plan.CurrentVersion = &planmanage.VersionRef{ID: value.CurrentVersion.ID}
	}
	if value.DefaultVersion != nil {
		plan.DefaultVersion = &planmanage.VersionRef{ID: value.DefaultVersion.ID}
	}
	if value.ArchivedBy != nil {
		actor := actorFromStore(*value.ArchivedBy)
		plan.ArchivedBy = &actor
	}
	return plan
}

func actorFromStore(value plan.Actor) planmanage.Actor {
	return planmanage.Actor{Type: value.Type, ID: value.ID, MemberID: value.MemberID}
}

func decodePageCursor(value string) (pageCursor, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return pageCursor{}, nil
	}
	raw, err := base64.RawURLEncoding.DecodeString(value)
	if err != nil {
		return pageCursor{}, storeerr.BadRequest("invalid Team Workflow cursor")
	}
	var cursor pageCursor
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&cursor); err != nil || cursor.UpdatedAt.IsZero() || strings.TrimSpace(cursor.ID) == "" {
		return pageCursor{}, storeerr.BadRequest("invalid Team Workflow cursor")
	}
	if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
		return pageCursor{}, storeerr.BadRequest("invalid Team Workflow cursor")
	}
	cursor.UpdatedAt = cursor.UpdatedAt.UTC()
	cursor.ID = strings.TrimSpace(cursor.ID)
	return cursor, nil
}

func encodePageCursor(cursor pageCursor) (string, error) {
	raw, err := json.Marshal(cursor)
	if err != nil {
		return "", fmt.Errorf("encode Team Workflow cursor: %w", err)
	}
	return base64.RawURLEncoding.EncodeToString(raw), nil
}
