package store

import (
	"context"
	"errors"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"

	"bountyboard/internal/domain"
	"bountyboard/internal/ulid"
)

func (s *Store) customers() *mongo.Collection { return s.DB.Collection("customers") }

func (s *Store) CreateCustomer(ctx context.Context, c *domain.Customer) error {
	now := time.Now().UTC()
	if c.ID == "" {
		c.ID = ulid.New()
	}
	c.CreatedAt, c.UpdatedAt, c.Version = now, now, 1
	if c.ConsultantIDs == nil {
		c.ConsultantIDs = []string{}
	}
	if _, err := s.customers().InsertOne(ctx, c); err != nil {
		if mongo.IsDuplicateKeyError(err) {
			return fmt.Errorf("customer %q: %w", c.Name, ErrDuplicate)
		}
		return fmt.Errorf("insert customer: %w", err)
	}
	return nil
}

func (s *Store) CustomerByID(ctx context.Context, id string) (*domain.Customer, error) {
	var c domain.Customer
	err := s.customers().FindOne(ctx, bson.M{"_id": id}).Decode(&c)
	if errors.Is(err, mongo.ErrNoDocuments) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("find customer: %w", err)
	}
	return &c, nil
}

// ListCustomers returns customers, optionally restricted to a consultant and
// optionally including archived ones, sorted by name.
func (s *Store) ListCustomers(ctx context.Context, consultantID string, includeArchived bool) ([]domain.Customer, error) {
	filter := bson.M{}
	if !includeArchived {
		filter["archived"] = false
	}
	if consultantID != "" {
		filter["consultantIds"] = consultantID
	}
	cur, err := s.customers().Find(ctx, filter)
	if err != nil {
		return nil, fmt.Errorf("list customers: %w", err)
	}
	out := []domain.Customer{}
	if err := cur.All(ctx, &out); err != nil {
		return nil, fmt.Errorf("decode customers: %w", err)
	}
	return out, nil
}

// UpdateCustomer applies a version-checked partial update.
func (s *Store) UpdateCustomer(ctx context.Context, id string, version int64, set bson.M) error {
	return UpdateVersioned(ctx, s.customers(), id, version, bson.M{"$set": set})
}

func (s *Store) DeleteCustomer(ctx context.Context, id string) error {
	res, err := s.customers().DeleteOne(ctx, bson.M{"_id": id})
	if err != nil {
		return fmt.Errorf("delete customer: %w", err)
	}
	if res.DeletedCount == 0 {
		return ErrNotFound
	}
	return nil
}

// SetSyncStatus records the outcome of a sync run (not version-checked:
// background bookkeeping must not race with admin edits).
func (s *Store) SetSyncStatus(ctx context.Context, id string, at time.Time, status, errMsg string) error {
	_, err := s.customers().UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": bson.M{
		"ticketing.lastSyncAt":     at,
		"ticketing.lastSyncStatus": status,
		"ticketing.lastSyncError":  errMsg,
	}})
	if err != nil {
		return fmt.Errorf("set sync status: %w", err)
	}
	return nil
}

// CountTasksForCustomer guards customer deletion.
func (s *Store) CountTasksForCustomer(ctx context.Context, customerID string) (int64, error) {
	n, err := s.DB.Collection("tasks").CountDocuments(ctx, bson.M{"customerId": customerID})
	if err != nil {
		return 0, fmt.Errorf("count tasks: %w", err)
	}
	return n, nil
}
