package account

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"code.revolvingcow.com/revolvingcow/loop/environment"
	"code.revolvingcow.com/revolvingcow/loop/security"
)

const (
	UserFile          = "loop_users"
	publishDateLayout = "200601021504"
)

type Account struct {
	Username   string `json:username`
	Passphrase string `json:passphrase`
}

func getAccounts() ([]Account, error) {
	file := UserFile

	// Check to see if the file exists
	if _, err := os.Stat(file); os.IsNotExist(err) {
		return []Account{}, nil
	}

	// Open the file
	fi, err := os.Open(file)
	if err != nil {
		return nil, err
	}

	// Read all of its contents
	raw, err := ioutil.ReadAll(fi)
	if len(raw) == 0 {
		return []Account{}, nil
	}

	// Translate the JSON to internal structures
	var accounts []Account
	err = json.Unmarshal(raw, &accounts)
	if err != nil {
		return nil, err
	}
	fi.Close()

	return accounts, nil
}

func (a *Account) Validate() (bool, error) {
	file := UserFile

	// Determine if the "users" file exists
	// Attempt to create the file if it does not
	if _, err := os.Stat(file); os.IsNotExist(err) {
		_, err = os.Create(file)
		if err != nil {
			return false, err
		}
	}

	// Get a list of current accounts
	accounts, err := getAccounts()
	if err != nil {
		return false, err
	}

	// Search if an account and passphrase match is found
	for _, x := range accounts {
		if x.Username == a.Username && x.Passphrase == a.Passphrase {
			return true, nil
		}
	}

	return false, nil
}

func (a *Account) Create() error {
	// Ensure there are no accounts of that name already
	valid, err := a.Validate()
	if err != nil {
		return err
	}
	if valid {
		return errors.New("Account already exists")
	}

	// Create directory structure
	baseDir := os.Getenv("LOOP_DATA")
	userDir := strings.ToLower(a.Username)
	s := string(filepath.Separator)
	paths := []string{"content", "blog", "calendar"}
	for _, p := range paths {
		err = os.MkdirAll(baseDir+s+userDir+s+p, 0755)
		if err != nil {
			return err
		}
	}

	// Add the account to all of the accounts
	accounts, err := getAccounts()
	if err != nil {
		return err
	}
	accounts = append(accounts, *a)

	// Create user entry in "users" file
	// Marshal the JSON
	marshalled, err := json.Marshal(accounts)
	if err != nil {
		return err
	}

	// Write to the file
	fi, err := os.OpenFile(UserFile, os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	_, err = fi.Write(marshalled)
	if err != nil {
		return err
	}
	fi.Close()

	return nil
}

func (a *Account) Delete() error {
	// Ensure there are no accounts of that name already
	valid, err := a.Validate()
	if err != nil {
		return err
	}
	if !valid {
		return errors.New("Account does not exist")
	}

	// Remove user entry in "users" file
	accounts, err := getAccounts()
	if err != nil {
		return err
	}

	for i, account := range accounts {
		if account.Username == a.Username {
			accounts = append(accounts[:i], accounts[i+1:]...)
			break
		}
	}

	marshalled, err := json.Marshal(accounts)
	if err != nil {
		return err
	}

	// Write to the file
	fi, err := os.OpenFile(UserFile, os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	_, err = fi.Write(marshalled)
	if err != nil {
		return err
	}
	fi.Close()

	// Delete directory structure
	baseDir := os.Getenv("LOOP_DATA")
	userDir := strings.ToLower(a.Username)
	s := string(filepath.Separator)
	err = os.RemoveAll(baseDir + s + userDir)
	if err != nil {
		return err
	}

	return nil
}

func (a *Account) Add(content, filename string, file []byte) (string, error) {
	if filename == "" {
		return "", errors.New("Missing filename")
	}

	valid, err := a.Validate()
	if err != nil {
		return "", err
	}
	if !valid {
		return "", errors.New("Account does not exist")
	}

	// Get basic information needed
	baseDir := os.Getenv("LOOP_DATA")
	userDir := strings.ToLower(a.Username)
	s := string(filepath.Separator)

	// Create new content directory if necessary
	if content == "" {
		content = fmt.Sprintf("%x", security.Hash(userDir+time.Now().Format(publishDateLayout), environment.Salt()))
	}

	// Create new content directory
	contentPath := baseDir + s + userDir + s + "content" + s + content
	if _, err := os.Stat(contentPath); os.IsNotExist(err) {
		err = os.MkdirAll(contentPath, 0755)
		if err != nil {
			return "", err
		}
	}

	// Check if filename already exists
	filePath := contentPath + s + filename
	if _, err := os.Stat(filePath); os.IsExist(err) {
		return filePath, err
	}

	// Write file to content directory
	fi, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		return filePath, err
	}
	_, err = fi.Write(file)
	if err != nil {
		return filePath, err
	}
	fi.Close()

	return filePath, nil
}

func (a *Account) Publish(title, content string, date time.Time) error {
	valid, err := a.Validate()
	if err != nil {
		return err
	}
	if !valid {
		return errors.New("Account does not exist")
	}

	prettyTitle := strings.ToLower(strings.Replace(title, " ", "-", -1))
	baseDir := os.Getenv("LOOP_DATA")
	userDir := strings.ToLower(a.Username)
	s := string(filepath.Separator)
	contentPath := baseDir + s + userDir + s + "content" + s + content
	publishPath := baseDir + s + userDir + s + "blog" + s + date.Format(publishDateLayout) + "-" + prettyTitle

	// Check if the content exists
	if _, err := os.Stat(contentPath); os.IsNotExist(err) {
		return err
	}

	// Check if symlink already exists
	if _, err := os.Stat(publishPath); os.IsExist(err) {
		return err
	}

	// Create symbolic link in "blog" directory referencing content directory
	err = os.Symlink(contentPath, publishPath)
	if err != nil {
		return err
	}

	return nil
}
