package invitationdb

import (
	"context"
	"fmt"

	"github.com/tech/sendico/pkg/db/repository"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.uber.org/zap"
)

func (db *InvitationDB) GetPublic(ctx context.Context, invitationRef bson.ObjectID) (*model.PublicInvitation, error) {
	roleField := repository.Field("role")
	orgField := repository.Field("organization")
	accField := repository.Field("account")
	empField := repository.Field("employee")
	regField := repository.Field("registrationAcc")
	descEmailField := repository.Field("description").Dot("email")
	pipeline := repository.Pipeline().
		// 0) Filter to exactly the invitation(s) you want
		Match(repository.IDFilter(invitationRef).And(repository.Filter("status", model.InvitationCreated))).
		// 1) Lookup the role document
		Lookup(
			mservice.Roles,
			repository.Field("roleRef"),
			repository.IDField(),
			roleField,
		).
		Unwind(repository.Ref(roleField)).
		// 2) Lookup the organization document
		Lookup(
			mservice.Organizations,
			repository.Field("organizationRef"),
			repository.IDField(),
			orgField,
		).
		Unwind(repository.Ref(orgField)).
		// 3) Lookup the account document
		Lookup(
			mservice.Accounts,
			repository.Field("inviterRef"),
			repository.IDField(),
			accField,
		).
		Unwind(repository.Ref(accField)).
		/* 4) do we already have an account whose login == invitation.description ? */
		Lookup(
			mservice.Accounts,
			descEmailField,            // local field (invitation.description.email)
			repository.Field("login"), // foreign field (account.login)
			regField,                  // array: 0-length or ≥1
		).
		// 5) Projection
		Project(
			repository.SimpleAlias(
				empField.Dot("description"),
				repository.Ref(accField),
			),
			repository.SimpleAlias(
				empField.Dot("avatarUrl"),
				repository.Ref(accField.Dot("avatarUrl")),
			),
			repository.SimpleAlias(
				orgField.Dot("description"),
				repository.Ref(orgField),
			),
			repository.SimpleAlias(
				orgField.Dot("logoUrl"),
				repository.Ref(orgField.Dot("logoUrl")),
			),
			repository.SimpleAlias(
				roleField,
				repository.Ref(roleField),
			),
			repository.SimpleAlias(
				repository.Field("invitation"),                  // ← left-hand side
				repository.Ref(repository.Field("description")), // ← right-hand side (“$description”)
			),
			repository.SimpleAlias(
				repository.Field("storable"), // ← left-hand side
				repository.RootRef(),         // ← right-hand side (“$description”)
			),
			repository.ProjectionExpr(
				repository.Field("registrationRequired"),
				repository.Eq(
					repository.Size(repository.Value(repository.Ref(regField).Build())),
					repository.Literal(0),
				),
			),
		)

	var res model.PublicInvitation
	haveResult := false
	decoder := func(cur *mongo.Cursor) error {
		if haveResult {
			// should never get here
			db.DBImp.Logger.Warn("Unexpected extra invitation", mzap.ObjRef("invitation_ref", invitationRef))
			return merrors.Internal("Unexpected extra invitation found by reference")
		}
		if e := cur.Decode(&res); e != nil {
			db.DBImp.Logger.Warn("Failed to decode entity", zap.Error(e), zap.Any("data", cur.Current.String()))
			return e
		}
		haveResult = true
		return nil
	}
	if err := db.DBImp.Repository.Aggregate(ctx, pipeline, decoder); err != nil {
		db.DBImp.Logger.Warn("Failed to execute aggregation pipeline", zap.Error(err), mzap.ObjRef("invitation_ref", invitationRef))
		return nil, err
	}
	if !haveResult {
		db.DBImp.Logger.Warn("No results fetched", mzap.ObjRef("invitation_ref", invitationRef))
		return nil, merrors.NoData(fmt.Sprintf("Invitation %s not found", invitationRef.Hex()))
	}
	return &res, nil
}
