// Package iamtest implements a fake IAM provider with the capability of
// inducing errors on any given operation, and retrospectively determining what
// operations have been carried out.
package iamtest

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/AdRoll/goamz/iam"
	"net"
	"net/http"
	"strings"
	"sync"
)

type action struct {
	srv   *Server
	w     http.ResponseWriter
	req   *http.Request
	reqId string
}

// Server implements an IAM simulator for use in tests.
type Server struct {
	reqId        int
	url          string
	listener     net.Listener
	users        []iam.User
	groups       []iam.Group
	accessKeys   []iam.AccessKey
	userPolicies []iam.UserPolicy
	mutex        sync.Mutex
}

func NewServer() (*Server, error) {
	l, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return nil, fmt.Errorf("cannot listen on localhost: %v", err)
	}
	srv := &Server{
		listener: l,
		url:      "http://" + l.Addr().String(),
	}
	go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		srv.serveHTTP(w, req)
	}))
	return srv, nil
}

// Quit closes down the server.
func (srv *Server) Quit() error {
	return srv.listener.Close()
}

// URL returns a URL for the server.
func (srv *Server) URL() string {
	return srv.url
}

type xmlErrors struct {
	XMLName string `xml:"ErrorResponse"`
	Error   iam.Error
}

func (srv *Server) error(w http.ResponseWriter, err *iam.Error) {
	w.WriteHeader(err.StatusCode)
	xmlErr := xmlErrors{Error: *err}
	if e := xml.NewEncoder(w).Encode(xmlErr); e != nil {
		panic(e)
	}
}

func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
	req.ParseForm()
	srv.mutex.Lock()
	defer srv.mutex.Unlock()
	action := req.FormValue("Action")
	if action == "" {
		srv.error(w, &iam.Error{
			StatusCode: 400,
			Code:       "MissingAction",
			Message:    "Missing action",
		})
	}
	if a, ok := actions[action]; ok {
		reqId := fmt.Sprintf("req%0X", srv.reqId)
		srv.reqId++
		if resp, err := a(srv, w, req, reqId); err == nil {
			if err := xml.NewEncoder(w).Encode(resp); err != nil {
				panic(err)
			}
		} else {
			switch err.(type) {
			case *iam.Error:
				srv.error(w, err.(*iam.Error))
			default:
				panic(err)
			}
		}
	} else {
		srv.error(w, &iam.Error{
			StatusCode: 400,
			Code:       "InvalidAction",
			Message:    "Invalid action: " + action,
		})
	}
}

func (srv *Server) createUser(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName"}); err != nil {
		return nil, err
	}
	path := req.FormValue("Path")
	if path == "" {
		path = "/"
	}
	name := req.FormValue("UserName")
	for _, user := range srv.users {
		if user.Name == name {
			return nil, &iam.Error{
				StatusCode: 409,
				Code:       "EntityAlreadyExists",
				Message:    fmt.Sprintf("User with name %s already exists.", name),
			}
		}
	}
	user := iam.User{
		Id:   "USER" + reqId + "EXAMPLE",
		Arn:  fmt.Sprintf("arn:aws:iam:::123456789012:user%s%s", path, name),
		Name: name,
		Path: path,
	}
	srv.users = append(srv.users, user)
	return iam.CreateUserResp{
		RequestId: reqId,
		User:      user,
	}, nil
}

func (srv *Server) getUser(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName"}); err != nil {
		return nil, err
	}
	name := req.FormValue("UserName")
	index, err := srv.findUser(name)
	if err != nil {
		return nil, err
	}
	return iam.GetUserResp{RequestId: reqId, User: srv.users[index]}, nil
}

func (srv *Server) deleteUser(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName"}); err != nil {
		return nil, err
	}
	name := req.FormValue("UserName")
	index, err := srv.findUser(name)
	if err != nil {
		return nil, err
	}
	copy(srv.users[index:], srv.users[index+1:])
	srv.users = srv.users[:len(srv.users)-1]
	return iam.SimpleResp{RequestId: reqId}, nil
}

func (srv *Server) createAccessKey(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName"}); err != nil {
		return nil, err
	}
	userName := req.FormValue("UserName")
	if _, err := srv.findUser(userName); err != nil {
		return nil, err
	}
	key := iam.AccessKey{
		Id:       fmt.Sprintf("%s%d", userName, len(srv.accessKeys)),
		Secret:   "",
		UserName: userName,
		Status:   "Active",
	}
	srv.accessKeys = append(srv.accessKeys, key)
	return iam.CreateAccessKeyResp{RequestId: reqId, AccessKey: key}, nil
}

func (srv *Server) deleteAccessKey(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"AccessKeyId", "UserName"}); err != nil {
		return nil, err
	}
	key := req.FormValue("AccessKeyId")
	index := -1
	for i, ak := range srv.accessKeys {
		if ak.Id == key {
			index = i
			break
		}
	}
	if index < 0 {
		return nil, &iam.Error{
			StatusCode: 404,
			Code:       "NoSuchEntity",
			Message:    "No such key.",
		}
	}
	copy(srv.accessKeys[index:], srv.accessKeys[index+1:])
	srv.accessKeys = srv.accessKeys[:len(srv.accessKeys)-1]
	return iam.SimpleResp{RequestId: reqId}, nil
}

func (srv *Server) listAccessKeys(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName"}); err != nil {
		return nil, err
	}
	userName := req.FormValue("UserName")
	if _, err := srv.findUser(userName); err != nil {
		return nil, err
	}
	var keys []iam.AccessKey
	for _, k := range srv.accessKeys {
		if k.UserName == userName {
			keys = append(keys, k)
		}
	}
	return iam.AccessKeysResp{
		RequestId:  reqId,
		AccessKeys: keys,
	}, nil
}

func (srv *Server) createGroup(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"GroupName"}); err != nil {
		return nil, err
	}
	name := req.FormValue("GroupName")
	path := req.FormValue("Path")
	for _, group := range srv.groups {
		if group.Name == name {
			return nil, &iam.Error{
				StatusCode: 409,
				Code:       "EntityAlreadyExists",
				Message:    fmt.Sprintf("Group with name %s already exists.", name),
			}
		}
	}
	group := iam.Group{
		Id:   "GROUP " + reqId + "EXAMPLE",
		Arn:  fmt.Sprintf("arn:aws:iam:::123456789012:group%s%s", path, name),
		Name: name,
		Path: path,
	}
	srv.groups = append(srv.groups, group)
	return iam.CreateGroupResp{
		RequestId: reqId,
		Group:     group,
	}, nil
}

func (srv *Server) listGroups(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	pathPrefix := req.FormValue("PathPrefix")
	if pathPrefix == "" {
		return iam.GroupsResp{
			RequestId: reqId,
			Groups:    srv.groups,
		}, nil
	}
	var groups []iam.Group
	for _, group := range srv.groups {
		if strings.HasPrefix(group.Path, pathPrefix) {
			groups = append(groups, group)
		}
	}
	return iam.GroupsResp{
		RequestId: reqId,
		Groups:    groups,
	}, nil
}

func (srv *Server) deleteGroup(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"GroupName"}); err != nil {
		return nil, err
	}
	name := req.FormValue("GroupName")
	index := -1
	for i, group := range srv.groups {
		if group.Name == name {
			index = i
			break
		}
	}
	if index == -1 {
		return nil, &iam.Error{
			StatusCode: 404,
			Code:       "NoSuchEntity",
			Message:    fmt.Sprintf("The group with name %s cannot be found.", name),
		}
	}
	copy(srv.groups[index:], srv.groups[index+1:])
	srv.groups = srv.groups[:len(srv.groups)-1]
	return iam.SimpleResp{RequestId: reqId}, nil
}

func (srv *Server) putUserPolicy(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName", "PolicyDocument", "PolicyName"}); err != nil {
		return nil, err
	}
	var exists bool
	policyName := req.FormValue("PolicyName")
	userName := req.FormValue("UserName")
	for _, policy := range srv.userPolicies {
		if policyName == policy.Name && userName == policy.UserName {
			exists = true
			break
		}
	}
	if !exists {
		policy := iam.UserPolicy{
			Name:     policyName,
			UserName: userName,
			Document: req.FormValue("PolicyDocument"),
		}
		var dumb interface{}
		if err := json.Unmarshal([]byte(policy.Document), &dumb); err != nil {
			return nil, &iam.Error{
				StatusCode: 400,
				Code:       "MalformedPolicyDocument",
				Message:    "Malformed policy document",
			}
		}
		srv.userPolicies = append(srv.userPolicies, policy)
	}
	return iam.SimpleResp{RequestId: reqId}, nil
}

func (srv *Server) deleteUserPolicy(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName", "PolicyName"}); err != nil {
		return nil, err
	}
	policyName := req.FormValue("PolicyName")
	userName := req.FormValue("UserName")
	index := -1
	for i, policy := range srv.userPolicies {
		if policyName == policy.Name && userName == policy.UserName {
			index = i
			break
		}
	}
	if index < 0 {
		return nil, &iam.Error{
			StatusCode: 404,
			Code:       "NoSuchEntity",
			Message:    "No such user policy",
		}
	}
	copy(srv.userPolicies[index:], srv.userPolicies[index+1:])
	srv.userPolicies = srv.userPolicies[:len(srv.userPolicies)-1]
	return iam.SimpleResp{RequestId: reqId}, nil
}

func (srv *Server) getUserPolicy(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
	if err := srv.validate(req, []string{"UserName", "PolicyName"}); err != nil {
		return nil, err
	}
	policyName := req.FormValue("PolicyName")
	userName := req.FormValue("UserName")
	index := -1
	for i, policy := range srv.userPolicies {
		if policyName == policy.Name && userName == policy.UserName {
			index = i
			break
		}
	}
	if index < 0 {
		return nil, &iam.Error{
			StatusCode: 404,
			Code:       "NoSuchEntity",
			Message:    "No such user policy",
		}
	}
	return iam.GetUserPolicyResp{
		Policy:    srv.userPolicies[index],
		RequestId: reqId,
	}, nil
}

func (srv *Server) findUser(userName string) (int, error) {
	var (
		err   error
		index = -1
	)
	for i, user := range srv.users {
		if user.Name == userName {
			index = i
			break
		}
	}
	if index < 0 {
		err = &iam.Error{
			StatusCode: 404,
			Code:       "NoSuchEntity",
			Message:    fmt.Sprintf("The user with name %s cannot be found.", userName),
		}
	}
	return index, err
}

// Validates the presence of required request parameters.
func (srv *Server) validate(req *http.Request, required []string) error {
	for _, r := range required {
		if req.FormValue(r) == "" {
			return &iam.Error{
				StatusCode: 400,
				Code:       "InvalidParameterCombination",
				Message:    fmt.Sprintf("%s is required.", r),
			}
		}
	}
	return nil
}

var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) (interface{}, error){
	"CreateUser":       (*Server).createUser,
	"DeleteUser":       (*Server).deleteUser,
	"GetUser":          (*Server).getUser,
	"CreateAccessKey":  (*Server).createAccessKey,
	"DeleteAccessKey":  (*Server).deleteAccessKey,
	"ListAccessKeys":   (*Server).listAccessKeys,
	"PutUserPolicy":    (*Server).putUserPolicy,
	"DeleteUserPolicy": (*Server).deleteUserPolicy,
	"GetUserPolicy":    (*Server).getUserPolicy,
	"CreateGroup":      (*Server).createGroup,
	"DeleteGroup":      (*Server).deleteGroup,
	"ListGroups":       (*Server).listGroups,
}
