package main

import (
	"encoding/json"
	"errors"
	"get.2cloud.org/twocloud"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

type tokenRequest struct {
	Access  string    `json:"access"`
	Refresh string    `json:"refresh,omitempty"`
	Expires time.Time `json:"expires,omitempty"`
}

type Credentials [2]string

func parseCallback(callback string) (*url.URL, error) {
	if callback == "" {
		return nil, errors.New("Callback must be specified.")
	}
	_, err := url.QueryUnescape(callback)
	if err != nil {
		return nil, errors.New("Bad callback formatting.")
	}
	cbURL, err := url.Parse(callback)
	if err != nil {
		return nil, errors.New("Invalid callback URL")
	}
	return cbURL, nil
}

func oauthRedirect(w http.ResponseWriter, r *twocloud.RequestBundle) {
	callback := r.Request.URL.Query().Get("callback")
	_, err := parseCallback(callback)
	if err != nil {
		Respond(w, r, http.StatusBadRequest, err.Error(), []interface{}{})
		return
	}
	url := r.GetOAuthAuthURL(r.Config.OAuth.ClientID, r.Config.OAuth.ClientSecret, r.Config.OAuth.CallbackURL, callback)
	http.Redirect(w, r.Request, url, http.StatusFound)
	return
}

func oauthCallback(w http.ResponseWriter, r *twocloud.RequestBundle) {
	code := r.Request.URL.Query().Get("code")
	if code == "" {
		Respond(w, r, http.StatusBadRequest, "No auth code specified.", []interface{}{})
		return
	}
	state := r.Request.URL.Query().Get("state")
	callback, err := parseCallback(state)
	if err != nil {
		Respond(w, r, http.StatusBadRequest, err.Error(), []interface{}{})
		return
	}
	access, refresh, exp, err := r.GetOAuthAccessToken(code)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error.", []interface{}{})
		return
	}
	account, err := r.GetAccount(access, refresh, exp)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error.", []interface{}{})
		return
	}
	values := callback.Query()
	if account.UserID != 0 {
		user, err := r.GetUser(account.UserID)
		if err != nil {
			r.Log.Error(err.Error())
			Respond(w, r, http.StatusInternalServerError, "Error while logging you in. We're looking into it.", []interface{}{})
			return
		}
		values.Set("user", user.Username)
		values.Set("secret", user.Secret)
	} else {
		values.Set("id", strconv.FormatUint(account.ID, 10))
		values.Set("email", account.Email)
		values.Set("givenName", account.GivenName)
		values.Set("familyName", account.FamilyName)
	}
	callback.RawQuery = values.Encode()
	http.Redirect(w, r.Request, callback.String(), http.StatusFound)
	return
}

func oauthToken(w http.ResponseWriter, r *twocloud.RequestBundle) {
	var tokens tokenRequest
	body, err := ioutil.ReadAll(r.Request.Body)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error.", []interface{}{})
		return
	}
	err = json.Unmarshal(body, &tokens)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusBadRequest, "Error decoding request.", []interface{}{})
		return
	}
	if tokens.Access == "" {
		Respond(w, r, http.StatusBadRequest, "access token must be supplied.", []interface{}{})
		return
	}
	account, err := r.GetAccount(tokens.Access, tokens.Refresh, tokens.Expires)
	if err != nil {
		r.Log.Error(err.Error())
		if err == twocloud.OAuthAuthError {
			Respond(w, r, http.StatusUnauthorized, err.Error(), []interface{}{})
			return
		} else if oauthError, ok := err.(twocloud.OAuthError); ok {
			Respond(w, r, http.StatusUnauthorized, oauthError.Error(), []interface{}{})
			return
		}
		Respond(w, r, http.StatusInternalServerError, "Internal server error.", []interface{}{})
		return
	}
	if account.UserID != 0 {
		user, err := r.GetUser(account.UserID)
		if err != nil {
			r.Log.Error(err.Error())
			Respond(w, r, http.StatusInternalServerError, "Error while logging you in. We're looking into it.", []interface{}{})
			return
		}
		setLastModified(w, user.LastActive)
		Respond(w, r, http.StatusOK, "Successfully authenticated the user", []interface{}{user})
		return
	}
	Respond(w, r, http.StatusCreated, "Successfully created a new account", []interface{}{account})
	setLastModified(w, account.Added)
	return
}

func updateAccountTokens(w http.ResponseWriter, r *twocloud.RequestBundle) {
	var tokens tokenRequest
	accountID := r.Request.URL.Query().Get(":account")
	if accountID == "" {
		Respond(w, r, http.StatusBadRequest, "Must specify an account ID.", []interface{}{})
		return
	}
	id, err := strconv.ParseUint(accountID, 10, 64)
	if err != nil {
		Respond(w, r, http.StatusBadRequest, "Invalid account ID.", []interface{}{})
		return
	}
	account, err := r.GetAccountByID(id)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	if account.UserID != r.AuthUser.ID && !r.AuthUser.IsAdmin {
		Respond(w, r, http.StatusForbidden, "You don't have access to that account.", []interface{}{})
		return
	}
	body, err := ioutil.ReadAll(r.Request.Body)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error.", []interface{}{})
		return
	}
	err = json.Unmarshal(body, &tokens)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusBadRequest, "Error decoding request.", []interface{}{})
		return
	}
	if tokens.Access == "" {
		Respond(w, r, http.StatusBadRequest, "access token must be supplied.", []interface{}{})
		return
	}
	err = r.UpdateAccountTokens(account, tokens.Access, tokens.Refresh, tokens.Expires)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	Respond(w, r, http.StatusOK, "Successfully updated the account tokens", []interface{}{account})
	return
}

func removeAccount(w http.ResponseWriter, r *twocloud.RequestBundle) {
	accountID := r.Request.URL.Query().Get(":account")
	if accountID == "" {
		Respond(w, r, http.StatusBadRequest, "Must specify an account ID.", []interface{}{})
		return
	}
	id, err := strconv.ParseUint(accountID, 10, 64)
	if err != nil {
		Respond(w, r, http.StatusBadRequest, "Invalid account ID.", []interface{}{})
		return
	}
	account, err := r.GetAccountByID(id)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	if account.UserID != r.AuthUser.ID && !r.AuthUser.IsAdmin {
		Respond(w, r, http.StatusForbidden, "You don't have access to that account.", []interface{}{})
		return
	}
	err = r.DeleteAccount(account)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	Respond(w, r, http.StatusOK, "Successfully deleted the account", []interface{}{account})
	return
}

func refreshAccount(w http.ResponseWriter, r *twocloud.RequestBundle) {
	accountID := r.Request.URL.Query().Get(":account")
	if accountID == "" {
		Respond(w, r, http.StatusBadRequest, "Must specify an account ID.", []interface{}{})
		return
	}
	id, err := strconv.ParseUint(accountID, 10, 64)
	if err != nil {
		Respond(w, r, http.StatusBadRequest, "Invalid account ID.", []interface{}{})
		return
	}
	account, err := r.GetAccountByID(id)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	if account.UserID != r.AuthUser.ID && !r.AuthUser.IsAdmin {
		Respond(w, r, http.StatusForbidden, "You don't have access to that account.", []interface{}{})
		return
	}
	account, err = r.UpdateAccountData(account)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	Respond(w, r, http.StatusOK, "Successfully updated the account", []interface{}{account})
	return
}

func generateTmpCredentials(w http.ResponseWriter, r *twocloud.RequestBundle) {
	strs, err := r.CreateTempCredentials(r.AuthUser)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error.", []interface{}{})
		return
	}
	creds := Credentials(strs)
	Respond(w, r, http.StatusCreated, "Generated temporary credentials", []interface{}{creds})
	return
}

func authTmpCredentials(w http.ResponseWriter, r *twocloud.RequestBundle) {
	cred1 := r.Request.URL.Query().Get("cred1")
	cred2 := r.Request.URL.Query().Get("cred2")
	if cred1 == "" || cred2 == "" {
		Respond(w, r, http.StatusBadRequest, "Both temporary credentials must be supplied", []interface{}{})
		return
	}
	id, err := r.CheckTempCredentials(cred1, cred2)
	if err == twocloud.InvalidCredentialsError {
		Respond(w, r, http.StatusUnauthorized, "Invalid credentials", []interface{}{})
		return
	} else if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	user, err := r.GetUser(id)
	if err != nil {
		r.Log.Error(err.Error())
		Respond(w, r, http.StatusInternalServerError, "Internal server error", []interface{}{})
		return
	}
	Respond(w, r, http.StatusOK, "Successfully authenticated the user", []interface{}{user})
	return
}

func auditAccount(w http.ResponseWriter, r *twocloud.RequestBundle) {
}