package multistore

import (
	"context"
	"fmt"
	"strings"

	"github.com/influxdata/influxdb/v2/chronograf"
)

// Ensure OrganizationsStore implements chronograf.OrganizationsStore.
var _ chronograf.OrganizationsStore = &OrganizationsStore{}

// OrganizationsStore implements the chronograf.OrganizationsStore interface, and
// delegates to all contained OrganizationsStores
type OrganizationsStore struct {
	Stores []chronograf.OrganizationsStore
}

// All concatenates the Organizations of all contained Stores
func (multi *OrganizationsStore) All(ctx context.Context) ([]chronograf.Organization, error) {
	all := []chronograf.Organization{}
	orgSet := map[string]struct{}{}

	ok := false
	var err error
	for _, store := range multi.Stores {
		var orgs []chronograf.Organization
		orgs, err = store.All(ctx)
		if err != nil {
			// If this Store is unable to return an array of orgs, skip to the
			// next Store.
			continue
		}
		ok = true // We've received a response from at least one Store
		for _, org := range orgs {
			// Enforce that the org has a unique ID
			// If the ID has been seen before, ignore the org
			if _, okay := orgSet[org.ID]; !okay { // We have a new org
				orgSet[org.ID] = struct{}{} // We just care that the ID is unique
				all = append(all, org)
			}
		}
	}
	if !ok {
		return nil, err
	}
	return all, nil
}

// Add the org to the first responsive Store
func (multi *OrganizationsStore) Add(ctx context.Context, org *chronograf.Organization) (*chronograf.Organization, error) {
	errors := []string{}
	for _, store := range multi.Stores {
		var o *chronograf.Organization
		o, err := store.Add(ctx, org)
		if err == nil {
			return o, nil
		}
		errors = append(errors, err.Error())
	}
	return nil, fmt.Errorf("unknown error while adding organization: %s", strings.Join(errors, " "))
}

// Delete delegates to all Stores, returns success if one Store is successful
func (multi *OrganizationsStore) Delete(ctx context.Context, org *chronograf.Organization) error {
	errors := []string{}
	for _, store := range multi.Stores {
		err := store.Delete(ctx, org)
		if err == nil {
			return nil
		}
		errors = append(errors, err.Error())
	}
	return fmt.Errorf("unknown error while deleting organization: %s", strings.Join(errors, " "))
}

// Get finds the Organization by id among all contained Stores
func (multi *OrganizationsStore) Get(ctx context.Context, query chronograf.OrganizationQuery) (*chronograf.Organization, error) {
	var err error
	for _, store := range multi.Stores {
		var o *chronograf.Organization
		o, err = store.Get(ctx, query)
		if err == nil {
			return o, nil
		}
	}
	return nil, chronograf.ErrOrganizationNotFound
}

// Update the first responsive Store
func (multi *OrganizationsStore) Update(ctx context.Context, org *chronograf.Organization) error {
	errors := []string{}
	for _, store := range multi.Stores {
		err := store.Update(ctx, org)
		if err == nil {
			return nil
		}
		errors = append(errors, err.Error())
	}
	return fmt.Errorf("unknown error while updating organization: %s", strings.Join(errors, " "))
}

// CreateDefault makes a default organization in the first responsive Store
func (multi *OrganizationsStore) CreateDefault(ctx context.Context) error {
	errors := []string{}
	for _, store := range multi.Stores {
		err := store.CreateDefault(ctx)
		if err == nil {
			return nil
		}
		errors = append(errors, err.Error())
	}
	return fmt.Errorf("unknown error while creating default organization: %s", strings.Join(errors, " "))
}

// DefaultOrganization returns the first successful DefaultOrganization
func (multi *OrganizationsStore) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) {
	errors := []string{}
	for _, store := range multi.Stores {
		org, err := store.DefaultOrganization(ctx)
		if err == nil {
			return org, nil
		}
		errors = append(errors, err.Error())
	}
	return nil, fmt.Errorf("unknown error while getting default organization: %s", strings.Join(errors, " "))

}
