package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

//abstract CRUD operation for espoCRM Entity

func crmCreateEntity(entityType string, jsonB []byte) (entity interface{}, err error) {
	url := CRMConfig.apiURL(entityType)
	jsonStr, err := postRAW(jsonB, url, crmBuildCommonAPIHeader())
	if err != nil {
		entity, _ = crmRescueDuplicateCreate(err, entityType)
		return
	}
	return crmJSON2Entity(entityType, jsonStr)
}

func crmUpdateEntity(entityType string, id string, jsonB []byte) (entity interface{}, err error) {
	url := CRMConfig.apiEntityURL(entityType, id)
	jsonStr, err := patchRAW(jsonB, url, crmBuildCommonAPIHeader())
	if err != nil {
		log.Println(err)
		return
	}
	return crmJSON2Entity(entityType, jsonStr)
}

func crmReplaceEntity(entityType string, id string, jsonB []byte) (entity interface{}, err error) {
	url := CRMConfig.apiEntityURL(entityType, id)
	jsonStr, err := putRAW(jsonB, url, crmBuildCommonAPIHeader())
	if err != nil {
		log.Println(err)
		return
	}
	return crmJSON2Entity(entityType, jsonStr)
}

func crmDeleteEntity(entityType string, id string) (deleted bool, err error) {
	url := CRMConfig.apiEntityURL(entityType, id)
	resp, err := deleteRAW(url, crmBuildCommonAPIHeader())
	if err != nil {
		log.Println(err)
		return
	}
	deleted = strings.ToLower(resp) == "true"
	return
}

//give an id, return json
func crmGetEntity(entityType string, id string) (entity interface{}, err error) {
	url := CRMConfig.apiEntityURL(entityType, id)
	jsonStr, err := getRAW(url, crmBuildCommonAPIHeader())
	if err != nil {
		log.Println(err)
		return
	}
	return crmJSON2Entity(entityType, jsonStr)
}

func crmBuildCommonAPIHeader() (headers map[string]string) {
	headers = map[string]string{}
	headers["Authorization"] = crmAuthHeader()
	headers["Accept"] = "application/json"
	headers["Content-Type"] = "application/json"
	return headers
}

//given a json string, convert it to Typed structure
func crmJSON2Entity(entityType string, data string) (r interface{}, err error) {
	switch entityType {
	case "Lead":
		e := crmdLead{}
		err = json.Unmarshal([]byte(data), &e)
		r = e
	case "Account":
		//r = crmdAccount{}
	default:
		log.Fatalf("crmJSON2Entity: Unknown EntityType %s", entityType)
	}

	if err != nil {
		log.Println(err)
	}
	return
}

func crmRescueDuplicateCreate(e error, entityType string) (entity interface{}, success bool) {
	if !isErrIndicateDuplicate(e) {
		return
	}
	entity, err := e.(errorHTTPResponse).XStatusReason().Data2Entity(entityType)
	success = err == nil
	return
}

func isErrIndicateDuplicate(e error) (yes bool) {
	err, isOurError := e.(errorHTTPResponse)
	if !isOurError {
		return
	}
	reason := err.XStatusReason()
	yes = strings.ToLower(reason.Reason) == "duplicate"
	return
}

type crmdSearchFilter struct {
	Attribute     string
	CompareMethod string //startWith, equals, contains, endsWith, like, isNull, isNotNull , notEquals
	ExpectValue   string
}

type crmdSearchResult struct {
	Total int              `json:"total"`
	List  *json.RawMessage `json:"list"`
}

func crmSearchEntity(entityType string, filters []crmdSearchFilter) (result crmdSearchResult, err error) {

	url := CRMConfig.apiURL(entityType)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Println("crmGetRecord: cannot form good http Request")
		log.Print(err)
		return
	}

	//auth header
	req.Header.Set("Authorization", crmAuthHeader())
	req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
	req.Header.Set("Content-Type", "application/json")

	//query string
	q := req.URL.Query()
	q.Add("maxSize", "20")
	q.Add("maxSize", "0")
	q.Add("sortBy", "createdAt")
	q.Add("asc", "false")

	for k, v := range filters {
		switch v.CompareMethod {
		case "startWith", "equals", "contains", "endsWith", "like", "notEquals":
			q.Add(fmt.Sprintf("where[%d][type]", k), v.CompareMethod)
			q.Add(fmt.Sprintf("where[%d][attribute]", k), v.Attribute)
			q.Add(fmt.Sprintf("where[%d][value]", k), v.ExpectValue)
		case "isNull", "isNotNull":
			q.Add(fmt.Sprintf("where[%d][type]", k), v.CompareMethod)
			q.Add(fmt.Sprintf("where[%d][attribute]", k), v.Attribute)
		default:
			log.Printf("Unknown Compare Method %s", v.CompareMethod)
		}
	}

	req.URL.RawQuery = q.Encode()

	//debugDumpHTTPRequest(req)

	client := &http.Client{}
	r, err := client.Do(req)

	//debugDumpHTTPResponse(r)

	if err != nil {
		return
	}
	defer r.Body.Close()

	jsonB, err := ioutil.ReadAll(r.Body)

	if err != nil {
		return
	}

	err = json.Unmarshal(jsonB, &result)
	return
}

func crmFindEntityByID(entityType string, id string) (entity interface{}, err error) {
	filters := []crmdSearchFilter{
		{"id", "equals", id},
	}
	cs, err := crmSearchEntity(entityType, filters)
	if err != nil || cs.Total < 1 {
		return
	}

	if cs.Total > 1 {
		log.Printf("ERROR: more than one(%d) %s associated with %s", cs.Total, entityType, id)
	}

	switch entityType {
	case "Lead":
		e := []crmdLead{}
		err = json.Unmarshal(*cs.List, &e)
		if (err != nil) || (len(e) != cs.Total) {
			return
		}
		entity = e[0]
	case "Account":
		return

	}
	return
}

func crmFindEntityByAttr(entityType, attribute, value string) (total int, list interface{}, err error) {
	filters := []crmdSearchFilter{
		{attribute, "equals", value},
	}

	cs, err := crmSearchEntity(entityType, filters)
	if err != nil || cs.Total < 1 {
		return
	}
	total = cs.Total
	switch entityType {
	case "Lead":
		e := []crmdLead{}
		err = json.Unmarshal(*cs.List, &e)
		list = e
	case "Account":
		return

	}
	return
}
