package main

import (
	"crypto/sha1"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"sort"
	"strings"
)

//apiV1Main  version 1 main entry for all wechat callbacks
//
func apiV1Main(w http.ResponseWriter, r *http.Request) {
	logRequestDebug(httputil.DumpRequest(r, true))
	if checkSignature(r) == false {
		log.Println("signature of URL incorrect")
		w.Header().Set("Content-Type", "text/xml; charset=utf-8")
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "") //empty string
		return
	}

	switch r.Method {
	case "POST":
		//answerWechatPostEcho(w, r)
		answerWechatPost(w, r)
	case "GET":
		answerInitialAuth(w, r)
	default:
		log.Println(fmt.Sprintf("FATAL: Unhandled HTTP %s", r.Method))
		response404Handler(w)
		//fmt.Fprintf(w, "Protocol Error: Expect GET or POST only")
	}
}

//
//answerInitialAuth,  when wechat first verify our URL for API hooks
//
func answerInitialAuth(w http.ResponseWriter, r *http.Request) {
	rq := r.URL.RawQuery
	m, _ := url.ParseQuery(rq)

	echostr, eok := m["echostr"]
	if checkSignature(r) && eok {
		fmt.Fprintf(w, echostr[0])
	} else {
		fmt.Fprintf(w, "wtf is wrong with the Internet")
	}
}

//
//answerWechatPost  distribute PostRequest according to xml body info
func answerWechatPost(w http.ResponseWriter, r *http.Request) {
	in, valid := readWechatInput(r)
	reply := "" //nothing
	w.Header().Set("Content-Type", "text/xml; charset=utf-8")

	if !valid {
		log.Println("Error:  Invalid Input ")
	} else {
		//put into user session based pipeline
		AllInMessage <- in
	}

	//read instant response
	reply = <-in.instantResponse
	in.destroy()
	//uncomment the followin two lines to enable echo test
	// state, _ := echoCommand(in.header.FromUserName, in)
	// reply = state.response
	fmt.Fprint(w, reply)
}

func readWechatInput(r *http.Request) (result InWechatMsg, valid bool) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Println(err)
		valid = false
		return
	}
	d := decryptToXML(string(body))
	if d == "" {
		log.Println("Cannot decode Message : \r\n" + string(body))
		valid = false
		return
	}
	valid = true
	fmt.Printf("decrypt as: %s\n", d)
	result.init()
	result.header = ReadCommonHeader(d)
	switch result.header.MsgType {
	case "text":
		result.body = ReadTextMsg(d)
	case "image":
		result.body = ReadPicMsg(d)
	case "voice":
		result.body = ReadVoiceMsg(d)
	case "video":
		result.body = ReadVideoMsg(d)
	case "shortvideo":
		result.body = ReadShortVideoMsg(d)
	case "location":
		result.body = ReadLocationMsg(d)
	case "link":
		result.body = ReadLinkMsg(d)
	case "event":
		result.body = ReadEventMsg(d)
	default:
		log.Println("Fatal: unknown incoming message type" + result.header.MsgType)
		valid = false
	}
	return
}

func answerWechatPostEcho(w http.ResponseWriter, r *http.Request) {
	body, _ := ioutil.ReadAll(r.Body)
	//fmt.Printf("get body: %s", string(body))
	s := ReadEncryptedMsg(string(body))
	//fmt.Printf("to decrypt %s", s.Encrypt)
	d := Decode(s.Encrypt)
	fmt.Printf("echo as: \r\n%s", d)

	e := strings.Replace(d, "ToUserName", "FDDD20170506xyzunique", 2)
	f := strings.Replace(e, "FromUserName", "ToUserName", 2)
	g := strings.Replace(f, "FDDD20170506xyzunique", "FromUserName", 2)
	fmt.Fprint(w, g)
}

//
func checkSignature(r *http.Request) bool {
	rq := r.URL.RawQuery
	m, _ := url.ParseQuery(rq)

	signature, sok := m["signature"]
	timestamp, tok := m["timestamp"]
	nonce, nok := m["nonce"]
	token := APIConfig.Token
	if sok && tok && nok {
		//sort token, timestamp, nonce  and join them
		strs := []string{token, timestamp[0], nonce[0]}
		sort.Strings(strs)
		s := strings.Join(strs, "")

		//calculate sha1
		h := sha1.New()
		h.Write([]byte(s))
		calculated := fmt.Sprintf("%x", h.Sum(nil))
		return signature[0] == calculated
	}
	return false
}

func checkSignature1() bool {
	s1 := "e39de9f2e28079c01ebb4b803dfc3442b819545c"
	t1 := "1492970761"
	n1 := "1850971833"
	token := APIConfig.Token

	strs := []string{token, t1, n1}
	sort.Strings(strs)
	s := strings.Join(strs, "")

	h := sha1.New()
	h.Write([]byte(s))
	us := fmt.Sprintf("%x", h.Sum(nil))
	return s1 == us
}

//webrootHandler sending contents to client when request "/"
//      essentially to prove the webserver is still alive
//      echo query string to the client
func webrootHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
	rq := r.URL.RawQuery
	m, _ := url.ParseQuery(rq)
	for index, element := range m {
		fmt.Fprintf(w, "\n%s => %s", index, element)
	}
	logRequestDebug(httputil.DumpRequest(r, true))

}

func logRequestDebug(data []byte, err error) {
	if err == nil {
		fmt.Printf("%s\n\n", string(data))
	} else {
		log.Fatalf("%s\n\n", err)
	}
}

//save uploaded 'file' into temporary location
func uploadHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(32 << 20)           //32MB memory
	file, handler, err := r.FormFile("file") //form-field 'file'
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()
	fmt.Fprintf(w, "%v", handler.Header)
	f, err := os.OpenFile("/tmp/wechat_hitxy_"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()
	io.Copy(f, file)
}

//when user requst an attachment from our server, we get the file from CRM server and reply it back
//in this way, user has no 'direct-access' to the CRM server.
func crmAttachmentHandler(w http.ResponseWriter, r *http.Request) {
	fileID := getCrmFileID(r.URL.Path)
	saveAs := GlobalPath.CRMAttachment + "crm_attach_" + fileID //add prefix for easy deleting
	if isFileExist(saveAs) {
		http.ServeFile(w, r, saveAs)
		return
	}
	if fileID == "" {
		log.Println("invalid fileID")
		response404Handler(w)
		return
	}
	log.Printf("download %s => %s", fileID, saveAs)
	crmDownloadAttachmentAs(fileID, saveAs)
	if !isFileExist(saveAs) {
		response404Handler(w)
		return
	}
	http.ServeFile(w, r, saveAs)
}

func getCrmFileID(urlPath string) string {
	return strings.TrimPrefix(urlPath, "/crmfiles/")
}

func response404Handler(w http.ResponseWriter) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprintf(w, "not found")
}
