package bitbucket

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/url"
	"time"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

type User struct {
	Name         string `json:"name,omitempty"`
	EmailAddress string `json:"emailAddress,omitempty"`
	DisplayName  string `json:"displayName,omitempty"`
	UserId       int    `json:"id,omitempty"`
}

type UserUpdate struct {
	Name         string `json:"name,omitempty"`
	EmailAddress string `json:"email,omitempty"`
	DisplayName  string `json:"displayName,omitempty"`
}

func resourceUser() *schema.Resource {
	return &schema.Resource{
		Create: resourceUserCreate,
		Update: resourceUserUpdate,
		Read:   resourceUserRead,
		Exists: resourceUserExists,
		Delete: resourceUserDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"email_address": {
				Type:     schema.TypeString,
				Required: true,
			},
			"display_name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"password_length": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      20,
				ForceNew:     true,
				ValidateFunc: validation.IntBetween(5, 128),
			},
			"initial_password": {
				Type:      schema.TypeString,
				Sensitive: true,
				Computed:  true,
			},
		},
	}
}

const passwordCharset = "abcdefghijklmnopqrstuvwxyz" +
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +
	"0123456789" +
	"@^*_-[]"

func generateUserPassword(length int) string {
	var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
	b := make([]byte, length)
	for i := range b {
		b[i] = passwordCharset[seededRand.Intn(len(passwordCharset))]
	}
	return string(b)
}

func newUserFromResource(d *schema.ResourceData) *User {
	user := &User{
		Name:         d.Get("name").(string),
		EmailAddress: d.Get("email_address").(string),
		DisplayName:  d.Get("display_name").(string),
	}

	return user
}

func newUserUpdateFromResource(d *schema.ResourceData) *UserUpdate {
	user := &UserUpdate{
		Name:         d.Get("name").(string),
		EmailAddress: d.Get("email_address").(string),
		DisplayName:  d.Get("display_name").(string),
	}

	return user
}

func resourceUserUpdate(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketServerProvider).BitbucketClient
	user := newUserUpdateFromResource(d)

	bytedata, err := json.Marshal(user)

	if err != nil {
		return err
	}

	_, err = client.Put("/rest/api/1.0/admin/users", bytes.NewBuffer(bytedata))

	if err != nil {
		return err
	}

	return resourceUserRead(d, m)
}

func resourceUserCreate(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketServerProvider).BitbucketClient
	user := newUserFromResource(d)

	passwordLength := d.Get("password_length").(int)
	initialPassword := generateUserPassword(passwordLength)
	d.Set("initial_password", initialPassword)

	_, err := client.Post(fmt.Sprintf("/rest/api/1.0/admin/users?name=%s&password=%s&displayName=%s&emailAddress=%s",
		url.QueryEscape(user.Name),
		url.QueryEscape(initialPassword),
		url.QueryEscape(user.DisplayName),
		url.QueryEscape(user.EmailAddress),
	), nil)

	if err != nil {
		return err
	}

	d.SetId(user.Name)

	return resourceUserRead(d, m)
}

func resourceUserRead(d *schema.ResourceData, m interface{}) error {
	id := d.Id()
	if id != "" {
		d.Set("name", id)
	}

	name := d.Get("name").(string)

	client := m.(*BitbucketServerProvider).BitbucketClient
	req, err := client.Get(fmt.Sprintf("/rest/api/1.0/users/%s",
		url.PathEscape(name),
	))

	if err != nil {
		return err
	}

	if req.StatusCode == 200 {

		var user User

		body, readerr := ioutil.ReadAll(req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &user)
		if decodeerr != nil {
			return decodeerr
		}

		d.Set("name", user.Name)
		d.Set("email_address", user.EmailAddress)
		d.Set("display_name", user.DisplayName)
		d.Set("user_id", user.UserId)
	}

	return nil
}

func resourceUserExists(d *schema.ResourceData, m interface{}) (bool, error) {
	var name = ""
	id := d.Id()
	if id != "" {
		name = id
	} else {
		name = d.Get("name").(string)
	}

	client := m.(*BitbucketServerProvider).BitbucketClient
	req, err := client.Get(fmt.Sprintf("/rest/api/1.0/users/%s",
		url.PathEscape(name),
	))

	if err != nil {
		return false, fmt.Errorf("failed to get user %s from bitbucket: %+v", name, err)
	}

	if req.StatusCode == 200 {
		return true, nil
	} else {
		return false, nil
	}
}

func resourceUserDelete(d *schema.ResourceData, m interface{}) error {
	name := d.Get("name").(string)
	client := m.(*BitbucketServerProvider).BitbucketClient
	_, err := client.Delete(fmt.Sprintf("/rest/api/1.0/admin/users?name=%s",
		url.QueryEscape(name),
	))

	return err
}
