package fcgiclient

import (
  "testing"
  "net"
  "net/http"
  "net/http/fcgi"
  "fmt"
  "time"
  "log"
  "io"
  "io/ioutil"
  "os"
  "bytes"
  "net/url"
  "crypto/md5"
  "math/rand"
  "path/filepath"
  "strconv"
  "strings"
  "encoding/binary"
)

// test fcgi protocol includes:
// Get, Post, Post in multipart/form-data, and Post with files
// each key should be the md5 of the value or the file uploaded
// sepicify remote fcgi responer ip:port to test with php
// test failed if the remote fcgi(script) failed md5 verification 
// and output "FAILED" in response
const (
  script_file = "/tank/www/fcgic_test.php"
  //ip_port = "remote-php-serv:59000"
  ip_port = "127.0.0.1:59000"
)

var (
  t_ *testing.T = nil
)

type FastCGIServer struct{}

func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {

  req.ParseMultipartForm(100000000);

  stat := "PASSED"
  fmt.Fprintln(resp, "-")
  file_num := 0
  {
    length := 0
    for k0, v0 := range req.Form {
      h := md5.New()
      io.WriteString(h, v0[0])
      md5 := fmt.Sprintf("%x", h.Sum(nil))
      
      length += len(k0)
      length += len(v0[0])
      
      // echo error when key != md5(val)
      if md5 != k0 {
        fmt.Fprintln(resp, "server:err ", md5, k0)
        stat = "FAILED"
      }
    }
    if req.MultipartForm != nil {
      file_num = len(req.MultipartForm.File)
      for kn, fns := range req.MultipartForm.File {
        //fmt.Fprintln(resp, "server:filekey ", kn )
        length += len(kn)
        for _, f := range fns {
          fd, err := f.Open()
          if err != nil {
            log.Println("server:", err)
            return
          }
          h := md5.New()
          l0, err := io.Copy(h,fd)
          if err != nil { log.Println(err); return}
          length += int(l0)
          defer fd.Close()
          md5 := fmt.Sprintf("%x", h.Sum(nil))
          //fmt.Fprintln(resp, "server:filemd5 ", md5 )
          
          if kn != md5 {
            fmt.Fprintln(resp, "server:err ", md5, kn)
            stat = "FAILED"
          }
          //fmt.Fprintln(resp, "server:filename ", f.Filename )
        }
      }
    }
    
    fmt.Fprintln(resp, "server:got data length", length)
  } 
  fmt.Fprintln(resp, "-"+stat+"-POST(", len(req.Form), ")-FILE(", file_num, ")--")
}

func sendFcgi(reqType int, fcgi_params map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) {
  fcgi, err := Dial("tcp", ip_port)  
  if err != nil {
    log.Println("err:", err)
    return
  }
  
  length := 0
  
  var resp *http.Response
  switch reqType {
  case 0:
    if len(data) > 0 {
      length = len(data)
      rd := bytes.NewReader(data)
      resp, err = fcgi.Post(fcgi_params, "", rd, rd.Len())
    } else if len(posts) > 0 {
      values := url.Values{}
      for k,v := range posts {
        values.Set(k, v)
        length += len(k)+2+len(v)
      }
      resp, err = fcgi.PostForm(fcgi_params, values)
    } else {
      resp, err = fcgi.Get(fcgi_params)
    }
    
  default:
    values := url.Values{}
    for k,v := range posts {
      values.Set(k, v)
      length += len(k)+2+len(v)
    }
    
    for k,v := range files {
      fi, _ := os.Lstat(v)
      length += len(k) + int(fi.Size())
    }
    resp, err = fcgi.PostFile(fcgi_params, values, files)
  }
  
  if err != nil {
    log.Println("err:", err)
    return
  }
  
  defer resp.Body.Close()
  content, err = ioutil.ReadAll(resp.Body)
  
  log.Println("c: send data length ≈", length, string(content))
  fcgi.Close()
  time.Sleep(1 * time.Second)
  
  if bytes.Index(content, []byte("FAILED")) >= 0 {
    t_.Error("Server return failed message")
  }

  return
}

func generateRandFile(size int) (p string, m string) {
  
  p = filepath.Join(os.TempDir(), "fcgict"+strconv.Itoa(rand.Int()))
  
  // open output file
  fo, err := os.Create(p)
  if err != nil { 
    panic(err) 
  }
  // close fo on exit and check for its returned error
  defer func() {
    if err := fo.Close(); err != nil {
      panic(err)
    }
  }()
  
  h := md5.New()  
  for i := 0; i < size/16; i++ {
    buf := make([]byte, 16)
    binary.PutVarint(buf, rand.Int63())
    fo.Write(buf)
    h.Write(buf)
  }
  m = fmt.Sprintf("%x", h.Sum(nil)) 
  return 
}

func Test(t *testing.T) {
  // TODO: test chunked reader
	
  t_ = t
  rand.Seed( time.Now().UTC().UnixNano())
  
  // server
  go func() {
    listener, err := net.Listen("tcp", ip_port)
    if err != nil {
      // handle error
      log.Println("listener creatation failed: ", err)
    }

    srv := new(FastCGIServer)
    fcgi.Serve(listener, srv) 
  }()
  
  time.Sleep(1 * time.Second)
  
  // init
  fcgi_params := make(map[string]string)
  fcgi_params["REQUEST_METHOD"] = "GET"
  fcgi_params["SERVER_PROTOCOL"] = "HTTP/1.1"
  //fcgi_params["GATEWAY_INTERFACE"] = "CGI/1.1"
  fcgi_params["SCRIPT_FILENAME"] = script_file

  // simple GET
  log.Println("test:", "get")
  sendFcgi(0, fcgi_params, nil, nil, nil)
  
  // simple post data
  log.Println("test:", "post")
  sendFcgi(0, fcgi_params, []byte("c4ca4238a0b923820dcc509a6f75849b=1&7b8b965ad4bca0e41ab51de7b31363a1=n"), nil, nil)

  log.Println("test:", "post data (more than 60KB)")
  data := ""
  length := 0
  for i := 0x00; i < 0xff; i++ {
    v0 := strings.Repeat(string(i), 256)
    h := md5.New()
    io.WriteString(h, v0)
    k0 := fmt.Sprintf("%x", h.Sum(nil))

    length += len(k0)
    length += len(v0)
    
    data += k0+"="+url.QueryEscape(v0)+"&"
  }
  sendFcgi(0, fcgi_params, []byte(data), nil, nil)
  
  
  log.Println("test:", "post form (use url.Values)")
  p0 := make(map[string]string, 1)
  p0["c4ca4238a0b923820dcc509a6f75849b"] = "1"
  p0["7b8b965ad4bca0e41ab51de7b31363a1"] = "n"
  sendFcgi(1, fcgi_params, nil, p0, nil)
  
  
  log.Println("test:", "post forms (256 keys, more than 1MB)")
  p1 := make(map[string]string, 1)
  for i := 0x00; i < 0xff; i++ {
    v0 := strings.Repeat(string(i), 4096)
    h := md5.New()
    io.WriteString(h, v0)
    k0 := fmt.Sprintf("%x", h.Sum(nil))
    p1[k0] = v0
  }
  sendFcgi(1, fcgi_params, nil, p1, nil)
  
  log.Println("test:", "post file (1 file, 500KB)) ")
  f0 := make(map[string]string, 1)
  path0,m0 := generateRandFile(500000)
  f0[m0]=path0
  sendFcgi(1, fcgi_params, nil, p1, f0)
  
  log.Println("test:", "post multiple files (2 files, 5M each) and forms (256 keys, more than 1MB data")
  path1,m1 := generateRandFile(5000000)
  f0[m1]=path1
  sendFcgi(1, fcgi_params, nil, p1, f0)
  
  log.Println("test:", "post only files (2 files, 5M each)" )
  sendFcgi(1, fcgi_params, nil, nil, f0)
  
  log.Println("test:", "post only 1 file")
  delete(f0, "m0")
  sendFcgi(1, fcgi_params, nil, nil, f0)
  
  os.Remove(path0)
  os.Remove(path1)
}
