package filestore

import (
	"context"
	"errors"
	"io/ioutil"
	"os"
	"path"

	"github.com/influxdata/chronograf"
)

// OrgExt is the the file extension searched for in the directory for org files
const OrgExt = ".org"

// Verify organizations implements organizationsStore interface.
var _ chronograf.OrganizationsStore = (*Organizations)(nil)

// Organizations are JSON orgs stored in the filesystem
type Organizations struct {
	Dir     string                                      // Dir is the directory containing the orgs.
	Load    func(string, interface{}) error             // Load loads string name and org passed in as interface
	ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.
	Logger  chronograf.Logger
}

// NewOrganizations constructs a org store wrapping a file system directory
func NewOrganizations(dir string, logger chronograf.Logger) chronograf.OrganizationsStore {
	return &Organizations{
		Dir:     dir,
		Load:    load,
		ReadDir: ioutil.ReadDir,
		Logger:  logger,
	}
}

// All returns all orgs from the directory
func (o *Organizations) All(ctx context.Context) ([]chronograf.Organization, error) {
	files, err := o.ReadDir(o.Dir)
	if err != nil {
		return nil, err
	}

	orgs := []chronograf.Organization{}
	for _, file := range files {
		if path.Ext(file.Name()) != OrgExt {
			continue
		}
		var org chronograf.Organization
		if err := o.Load(path.Join(o.Dir, file.Name()), &org); err != nil {
			continue // We want to load all files we can.
		} else {
			orgs = append(orgs, org)
		}
	}
	return orgs, nil
}

// Get returns a org file from the org directory
func (o *Organizations) Get(ctx context.Context, query chronograf.OrganizationQuery) (*chronograf.Organization, error) {
	org, _, err := o.findOrg(query)
	return org, err
}

// findOrg takes an OrganizationQuery and finds the associated filename
func (o *Organizations) findOrg(query chronograf.OrganizationQuery) (*chronograf.Organization, string, error) {
	// Because the entire org information is not known at this point, we need
	// to try to find the name of the file through matching the ID or name in the org
	// content with the ID passed.
	files, err := o.ReadDir(o.Dir)
	if err != nil {
		return nil, "", err
	}

	for _, f := range files {
		if path.Ext(f.Name()) != OrgExt {
			continue
		}
		file := path.Join(o.Dir, f.Name())
		var org chronograf.Organization
		if err := o.Load(file, &org); err != nil {
			return nil, "", err
		}
		if query.ID != nil && org.ID == *query.ID {
			return &org, file, nil
		}
		if query.Name != nil && org.Name == *query.Name {
			return &org, file, nil
		}
	}

	return nil, "", chronograf.ErrOrganizationNotFound
}

// Add is not allowed for the filesystem organization store
func (o *Organizations) Add(ctx context.Context, org *chronograf.Organization) (*chronograf.Organization, error) {
	return nil, errors.New("unable to add organizations to the filesystem")
}

// Delete is not allowed for the filesystem organization store
func (o *Organizations) Delete(ctx context.Context, org *chronograf.Organization) error {
	return errors.New("unable to delete an organization from the filesystem")
}

// Update is not allowed for the filesystem organization store
func (o *Organizations) Update(ctx context.Context, org *chronograf.Organization) error {
	return errors.New("unable to update organizations on the filesystem")
}

// CreateDefault is not allowed for the filesystem organization store
func (o *Organizations) CreateDefault(ctx context.Context) error {
	return errors.New("unable to create default organizations on the filesystem")
}

// DefaultOrganization is not allowed for the filesystem organization store
func (o *Organizations) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) {
	return nil, errors.New("unable to get default organizations from the filestore")
}
