package deliberi

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	indigoxrpc "github.com/bluesky-social/indigo/xrpc"
	"github.com/samber/lo"
	"tangled.org/core/api/org_tangled"
)

type bobbinLookup interface {
	RepoOwner(ctx context.Context, repoDid string) (ownerDid, name string, err error)
	TicketAuthor(ctx context.Context, ticketUri string) (string, error)
}

type inviteLister interface {
	ListCollaboratorInvitesBy(ctx context.Context, subjectDid string) (InviteAnswer, error)
}

type InviteOffer struct {
	Uri       string    `json:"uri"`
	KnotDid   string    `json:"knot"`
	RepoDid   string    `json:"repo"`
	AddedBy   string    `json:"addedBy"`
	CreatedAt time.Time `json:"createdAt"`
}

type InviteAnswer struct {
	Offers    []InviteOffer `json:"items"`
	Pending   []string      `json:"pending"`
	Truncated bool          `json:"truncated"`
}

type bobbinClient struct {
	xc *indigoxrpc.Client
}

func newBobbinClient(apiUrl string) *bobbinClient {
	return &bobbinClient{
		xc: &indigoxrpc.Client{
			Host:   strings.TrimRight(apiUrl, "/"),
			Client: &http.Client{Timeout: 10 * time.Second},
		},
	}
}

// the record can't name the opener once someone else edits it, so ask bobbin
func (c *bobbinClient) TicketAuthor(ctx context.Context, ticketUri string) (string, error) {
	out, err := org_tangled.TrackGetTicketView(ctx, c.xc, ticketUri)
	if err != nil {
		return "", fmt.Errorf("calling %s: %w", org_tangled.TrackGetTicketViewNSID, err)
	}
	if out.Author == nil {
		return "", fmt.Errorf("ticket view for %s has no author", ticketUri)
	}
	return out.Author.Did, nil
}

// bobbin serves getRepoByRepoDid from the repo's org.tangled.repo.manifest
type repoByRepoDid struct {
	Value repoManifest `json:"value"`
}

func (c *bobbinClient) RepoOwner(ctx context.Context, repoDid string) (string, string, error) {
	var out repoByRepoDid
	params := map[string]any{"repoDid": repoDid}
	if err := c.xc.Do(ctx, indigoxrpc.Query, "", org_tangled.RepoGetRepoByRepoDidNSID, params, nil, &out); err != nil {
		return "", "", fmt.Errorf("calling %s: %w", org_tangled.RepoGetRepoByRepoDidNSID, err)
	}

	decl := out.Value.Declaration
	if decl == nil || decl.Owner == "" {
		return "", "", fmt.Errorf("manifest for %s has no declared owner", repoDid)
	}
	return decl.Owner, decl.Name, nil
}

func (c *bobbinClient) ListCollaboratorInvitesBy(ctx context.Context, subjectDid string) (InviteAnswer, error) {
	var out InviteAnswer
	params := map[string]any{"subject": subjectDid}
	if err := c.xc.Do(ctx, indigoxrpc.Query, "", org_tangled.RepoListCollaboratorInvitesByNSID, params, nil, &out); err != nil {
		return InviteAnswer{}, fmt.Errorf("calling %s: %w", org_tangled.RepoListCollaboratorInvitesByNSID, err)
	}

	if blank, found := lo.Find(out.Offers, func(offer InviteOffer) bool {
		return offer.Uri == "" || offer.KnotDid == "" || offer.RepoDid == ""
	}); found {
		missing := "record"
		if blank.Uri != "" {
			missing = lo.Ternary(blank.KnotDid == "", "knot", "repo")
		}
		return InviteAnswer{}, fmt.Errorf(
			"%s served offer missing its %s, so we couldn't tell it apart from a withdrawal",
			org_tangled.RepoListCollaboratorInvitesByNSID,
			missing,
		)
	}

	return out, nil
}
