package databag

import (
  "io/ioutil"
  "errors"
  "bytes"
  "mime/multipart"
  "strings"
  "strconv"
  "time"
  "net/url"
  "net/http"
  "encoding/json"
  "net/http/httptest"
  "github.com/gorilla/mux"
  "github.com/gorilla/websocket"
)

const TEST_READDEADLINE = 2
const TEST_REVISIONWAIT = 100

type TestCard struct {
  Guid string
  Token string
  CardId string
  GroupId string
}

type TestContact struct {
  Guid string
  Token string
  Revisions chan *Revision
  A TestCard
  B TestCard
  C TestCard
  D TestCard
}

type TestGroup struct {
  A TestContact
  B TestContact
  C TestContact
  D TestContact
}

func GetTestRevision(status chan *Revision) (rev *Revision) {
  time.Sleep(TEST_REVISIONWAIT * time.Millisecond)
  for {
		select {
		case r:=<-status:
      rev = r
		default:
      return
		}
	}
}

func ApiTestData(
      endpoint func(http.ResponseWriter, *http.Request),
      requestType string,
      name string,
      params *map[string]string,
      body interface{},
      tokenType string,
      token string,
      start int64,
      end int64,
    ) (data []byte, hdr map[string][]string, err error) {

  var r *http.Request
  var w *httptest.ResponseRecorder

  if r, w, err = NewRequest(requestType, name, body); err != nil {
    return
  }
  if params != nil {
    r = mux.SetURLVars(r, *params)
  }
  if token != "" {
    r.Header.Add("TokenType", tokenType)
    SetBearerAuth(r, token)
  }
  if start != 0 || end != 0 {
    byteRange := "bytes=" + strconv.FormatInt(start, 10) + "-" + strconv.FormatInt(end, 10)
    r.Header.Add("Range", byteRange)
  }
  endpoint(w, r)

  resp := w.Result()
  if resp.StatusCode != 200 && resp.StatusCode != 206 {
    err = errors.New("response failed");
    return
  }
  hdr = resp.Header
  data, err = ioutil.ReadAll(resp.Body)
  return
}

func ApiTestMsg(
      endpoint func(http.ResponseWriter, *http.Request),
      requestType string,
      name string,
      params *map[string]string,
      body interface{},
      tokenType string,
      token string,
      response interface{},
      responseHeader *map[string][]string,
    ) (err error) {

  var r *http.Request
  var w *httptest.ResponseRecorder

  if r, w, err = NewRequest(requestType, name, body); err != nil {
    return
  }
  if params != nil {
    r = mux.SetURLVars(r, *params)
  }
  if tokenType != "" {
    r.Header.Add("TokenType", tokenType)
  }
  if token != "" {
    SetBearerAuth(r, token)
  }
  endpoint(w, r)

  resp := w.Result()
  if resp.StatusCode != 200 {
    err = errors.New("response failed");
    return
  }
  if responseHeader != nil {
    *responseHeader = resp.Header
  }
  if response == nil {
    return
  }
  dec := json.NewDecoder(resp.Body)
  dec.Decode(response)
  return
}

func ApiTestUpload(
      endpoint func(http.ResponseWriter, *http.Request),
      requestType string,
      name string,
      params *map[string]string,
      body []byte,
      tokenType string,
      token string,
      response interface{},
      responseHeader *map[string][]string,
    ) (err error) {

  data := bytes.Buffer{}
	writer := multipart.NewWriter(&data)
	part, err := writer.CreateFormFile("asset", "asset")
	if err != nil {
		return err
	}
	part.Write(body)
  if err = writer.Close(); err != nil {
    return
  }

  w := httptest.NewRecorder()
  r := httptest.NewRequest(requestType, name, &data)

  if params != nil {
    r = mux.SetURLVars(r, *params)
  }
  if tokenType != "" {
    r.Header.Add("TokenType", tokenType)
  }
  if token != "" {
    SetBearerAuth(r, token)
  }
  r.Header.Set("Content-Type", writer.FormDataContentType())
  endpoint(w, r)

  resp := w.Result()
  if resp.StatusCode != 200 {
    err = errors.New("response failed");
    return
  }
  if responseHeader != nil {
    *responseHeader = resp.Header
  }
  if response == nil {
    return
  }
  dec := json.NewDecoder(resp.Body)
  dec.Decode(response)
  return
}


//
//    A --- connected,group               connected,group --- B
//    | \                                                    /|
//    |   requested,nogroup                  confirmed,group  |
//    |                                                       |
//     connected,group                                       ,
//                                  |
//                                --x--
//                                  |
//     connected,group                                       ,
//    |                                                       |
//    |  ,                                   pending,nogroup  |
//    |/                                                     \|
//    C --- connected,group               connected,group --- D
//
func AddTestGroup(prefix string) (*TestGroup, error) {
    var err error
    var rev *Revision
    var ws *websocket.Conn

    // allocate contacts
    g := &TestGroup{}
    if g.A.Guid, g.A.Token, err = AddTestAccount(prefix+"A"); err != nil {
      return g, err
    }
    if g.B.Guid, g.B.Token, err = AddTestAccount(prefix+"B"); err != nil {
      return g, err
    }
    if g.C.Guid, g.C.Token, err = AddTestAccount(prefix+"C"); err != nil {
      return g, err
    }
    if g.D.Guid, g.D.Token, err = AddTestAccount(prefix+"D"); err != nil {
      return g, err
    }

    // setup A
    if g.A.B.CardId, err = AddTestCard(g.A.Token, g.B.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.A.Token, g.A.B.CardId); err != nil {
      return g, err
    }
    if g.A.B.GroupId, err = GroupTestCard(g.A.Token, g.A.B.CardId); err != nil {
      return g, err
    }
    if g.A.C.CardId, err = AddTestCard(g.A.Token, g.C.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.A.Token, g.A.C.CardId); err != nil {
      return g, err
    }
    if g.A.C.GroupId, err = GroupTestCard(g.A.Token, g.A.C.CardId); err != nil {
      return g, err
    }
    if g.A.D.CardId, err = AddTestCard(g.A.Token, g.D.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.A.Token, g.A.D.CardId); err != nil {
      return g, err
    }

    // setup B
    if g.B.A.CardId, err = AddTestCard(g.B.Token, g.A.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.B.Token, g.B.A.CardId); err != nil {
      return g, err
    }
    if g.B.A.GroupId, err = GroupTestCard(g.B.Token, g.B.A.CardId); err != nil {
      return g, err
    }
    if g.B.C.CardId, err = AddTestCard(g.B.Token, g.C.Token); err != nil {
      return g, err
    }
    if g.B.C.GroupId, err = GroupTestCard(g.B.Token, g.B.C.CardId); err != nil {
      return g, err
    }

    // setup C
    if g.C.D.CardId, err = AddTestCard(g.C.Token, g.D.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.C.Token, g.C.D.CardId); err != nil {
      return g, err
    }
    if g.C.D.GroupId, err = GroupTestCard(g.C.Token, g.C.D.CardId); err != nil {
      return g, err
    }
    if g.C.A.CardId, err = AddTestCard(g.C.Token, g.A.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.C.Token, g.C.A.CardId); err != nil {
      return g, err
    }
    if g.C.A.GroupId, err = GroupTestCard(g.C.Token, g.C.A.CardId); err != nil {
      return g, err
    }

    // setup D
    if g.D.C.CardId, err = AddTestCard(g.D.Token, g.C.Token); err != nil {
      return g, err
    }
    if err = OpenTestCard(g.D.Token, g.D.C.CardId); err != nil {
      return g, err
    }
    if g.D.C.GroupId, err = GroupTestCard(g.D.Token, g.D.C.CardId); err != nil {
      return g, err
    }
    if g.D.A.CardId, err = GetCardId(g.D.Token, g.A.Guid); err != nil {
      return g, err
    }

    // get contact tokens
    if g.A.B.Token, err = GetCardToken(g.A.Token, g.A.B.CardId); err != nil {
      return g, err
    }
    if g.B.A.Token, err = GetCardToken(g.B.Token, g.B.A.CardId); err != nil {
      return g, err
    }
    if g.C.A.Token, err = GetCardToken(g.C.Token, g.C.A.CardId); err != nil {
      return g, err
    }
    if g.C.D.Token, err = GetCardToken(g.C.Token, g.C.D.CardId); err != nil {
      return g, err
    }
    if g.D.C.Token, err = GetCardToken(g.D.Token, g.D.C.CardId); err != nil {
      return g, err
    }

    // connect websockets
    rev = &Revision{}
    if ws, err = StatusConnection(g.A.Token, rev); err != nil {
      return g, err
    }
    g.A.Revisions = make(chan *Revision, 64)
    g.A.Revisions <- rev
    go MonitorStatus(ws, &g.A);
    rev = &Revision{}
    if ws, err = StatusConnection(g.B.Token, rev); err != nil {
      return g, err
    }
    g.B.Revisions = make(chan *Revision, 64)
    g.B.Revisions <- rev
    go MonitorStatus(ws, &g.B);
    rev = &Revision{}
    if ws, err = StatusConnection(g.C.Token, rev); err != nil {
      return g, err
    }
    g.C.Revisions = make(chan *Revision, 64)
    g.C.Revisions <- rev
    go MonitorStatus(ws, &g.C);
    rev = &Revision{}
    if ws, err = StatusConnection(g.D.Token, rev); err != nil {
      return g, err
    }
    g.D.Revisions = make(chan *Revision, 64)
    g.D.Revisions <- rev
    go MonitorStatus(ws, &g.D);

    return g, nil
}


func MonitorStatus(ws *websocket.Conn, contact *TestContact) {
  var data []byte
  var dataType int
  var err error

  // reset any timeout
  ws.SetReadDeadline(time.Time{})

  // read revision update
  for ;; {
    if dataType, data, err = ws.ReadMessage(); err != nil {
      LogMsg("failed to read status conenction")
      return
    }
    if dataType != websocket.TextMessage {
      LogMsg("invalid status data type")
      return
    }
    rev := &Revision{}
    if err = json.Unmarshal(data, rev); err != nil {
      LogMsg("invalid status data")
      return
    }
    contact.Revisions <- rev
  }
}

func GetCardToken(account string, cardId string) (token string, err error) {
  var r *http.Request
  var w *httptest.ResponseRecorder
  var cardDetail CardDetail
  var cardProfile CardProfile
  vars := make(map[string]string)
  vars["cardId"] = cardId

  if r, w, err = NewRequest("GET", "/contact/cards/{cardId}/detail", nil); err != nil {
    return
  }
  r = mux.SetURLVars(r, vars)
  SetBearerAuth(r, account)
  GetCardDetail(w, r)
  if err = ReadResponse(w, &cardDetail); err != nil {
    return
  }
  if cardDetail.Status != APP_CARDCONNECTED {
    err = errors.New("card not connected")
    return
  }

  if r, w, err = NewRequest("GET", "/contact/cards/{cardId}/profile", nil); err != nil {
    return
  }
  r = mux.SetURLVars(r, vars)
  SetBearerAuth(r, account)
  GetCardProfile(w, r)
  if err = ReadResponse(w, &cardProfile); err != nil {
    return
  }

  token = cardProfile.Guid + "." + cardDetail.Token
  return
}

func GetCardId(account string, guid string) (cardId string, err error) {
  var r *http.Request
  var w *httptest.ResponseRecorder
  var cards []Card

  if r, w, err = NewRequest("GET", "/contact/cards", nil); err != nil {
    return
  }
  SetBearerAuth(r, account)
  GetCards(w, r)
  if err = ReadResponse(w, &cards); err != nil {
    return
  }

  for _, card := range cards {
    if card.Data.CardProfile.Guid == guid {
      cardId = card.Id
      return
    }
  }
  err = errors.New("card not found")
  return
}

func GroupTestCard(account string, cardId string) (groupId string, err error) {
  var r *http.Request
  var w *httptest.ResponseRecorder
  var subject *Subject
  var group Group
  var cardData CardData
  vars := make(map[string]string)

  // add new group
  subject = &Subject{
    DataType: "imagroup",
    Data: "group data with name and logo",
  }
  if r, w, err = NewRequest("POST", "/share/groups", subject); err != nil {
    return
  }
  SetBearerAuth(r, account)
  AddGroup(w, r)
  if err = ReadResponse(w, &group); err != nil {
    return
  }
  groupId = group.Id

  // set contact group
  if r, w, err = NewRequest("PUT", "/contact/cards/{cardId}/groups/{groupId}", nil); err != nil {
    return
  }
  vars["groupId"] = group.Id
  vars["cardId"] = cardId
  r = mux.SetURLVars(r, vars)
  SetBearerAuth(r, account)
  SetCardGroup(w, r)
  if err = ReadResponse(w, &cardData); err != nil {
    return
  }
  return
}

func OpenTestCard(account string, cardId string) (err error) {
  var r *http.Request
  var w *httptest.ResponseRecorder
  var msg DataMessage
  var card Card
  var vars = map[string]string{ "cardId": cardId }
  var contactStatus ContactStatus

  // set to connecting state
  if r, w, err = NewRequest("PUT", "/contact/cards/{cardId}/status", APP_CARDCONNECTING); err != nil {
    return
  }
  r = mux.SetURLVars(r, vars)
  SetBearerAuth(r, account)
  SetCardStatus(w, r)
  if err = ReadResponse(w, &card); err != nil {
    return
  }

  // get open message
  if r, w, err = NewRequest("GET", "/contact/cards/{cardId}/openMessage", nil); err != nil {
    return
  }
  r = mux.SetURLVars(r, vars)
  SetBearerAuth(r, account)
  GetOpenMessage(w, r)
  if err = ReadResponse(w, &msg); err != nil {
    return
  }

  // set open message 
  if r, w, err = NewRequest("PUT", "/contact/openMessage", msg); err != nil {
    return
  }
  SetOpenMessage(w, r)
  if err = ReadResponse(w, &contactStatus); err != nil {
    return
  }

  // update status if connected
  if contactStatus.Status == APP_CARDCONNECTED {
    view := "viewRevision=" + strconv.FormatInt(contactStatus.ViewRevision, 10)
    article := "articleRevision=" + strconv.FormatInt(contactStatus.ArticleRevision, 10)
    channel := "channelRevision=" + strconv.FormatInt(contactStatus.ChannelRevision, 10)
    profile := "profileRevision=" + strconv.FormatInt(contactStatus.ProfileRevision, 10)
    if r, w, err = NewRequest("PUT", "/contact/cards/{cardId}/status?token=" + contactStatus.Token + "&" + view + "&" + article + "&" + channel + "&" + profile, APP_CARDCONNECTED); err != nil {
      return
    }
    r = mux.SetURLVars(r, vars)
    SetBearerAuth(r, account)
    SetCardStatus(w, r)
    if err = ReadResponse(w, &card); err != nil {
      return
    }
  }
  return
}

func AddTestCard(account string, contact string) (cardId string, err error) {
  var r *http.Request
  var w *httptest.ResponseRecorder
  var msg DataMessage
  var card Card

  // get A identity message
  if r, w, err = NewRequest("GET", "/profile/message", nil); err != nil {
    return
  }
  r.Header.Add("TokenType", APP_TOKENAPP)
  SetBearerAuth(r, contact)
  GetProfileMessage(w, r)
  if err = ReadResponse(w, &msg); err != nil {
    return
  }

  // add A card in B
  if r, w, err = NewRequest("POST", "/contact/cards", &msg); err != nil {
    return
  }
  SetBearerAuth(r, account)
  AddCard(w, r)
  if err = ReadResponse(w, &card); err != nil {
    return
  }
  cardId = card.Id
  return
}

func AddTestAccount(username string) (guid string, token string, err error) {
  var r *http.Request
  var w *httptest.ResponseRecorder

  var access string
  app := AppData{
    Name: "Appy",
    Description: "A test app",
    Url: "http://app.example.com",
  };
  var claim Claim
  var msg DataMessage
  var profile Profile
  var login = username + ":pass"

  // get account token
  if r, w, err= NewRequest("POST", "/admin/accounts", nil); err != nil {
    return
  }
  SetBasicAuth(r, "admin:pass")
  AddNodeAccount(w, r)
  if err = ReadResponse(w, &access); err != nil {
    return
  }

  // set account profile
  if r, w, err = NewRequest("POST", "/account/profile", nil); err != nil {
    return
  }
  SetBearerAuth(r, access);
  SetCredentials(r, login)
  AddAccount(w, r)
  if err = ReadResponse(w, &profile); err != nil {
    return
  }
  guid = profile.Guid

  // acquire new token for attaching app
  if r, w, err = NewRequest("POST", "/account/apps", &app); err != nil {
    return
  }
  SetBasicAuth(r, login);
  AddAccountApp(w, r);
  if err = ReadResponse(w, &access); err != nil {
    return
  }
  token = access

  // authorize claim
  if r, w, err = NewRequest("PUT", "/authorize", "1234abcd"); err != nil {
    return
  }
  SetBearerAuth(r, token)
  Authorize(w, r)
  if err = ReadResponse(w, &msg); err != nil {
    return
  }
  signer, messageType, _, res := ReadDataMessage(&msg, &claim)
  if res != nil || signer != guid || messageType != APP_MSGAUTHENTICATE || claim.Token != "1234abcd" {
    err = errors.New("invalid authenticated claim")
    return
  }
  return
}

func NewRequest(rest string, path string, obj interface{}) (*http.Request, *httptest.ResponseRecorder, error) {
  w := httptest.NewRecorder()
  if(obj != nil) {
    body, err := json.Marshal(obj)
    if err != nil {
      return nil, nil, err
    }
    reader := strings.NewReader(string(body))
    return httptest.NewRequest(rest, path, reader), w, nil
  }

  return httptest.NewRequest(rest, path, nil), w, nil
}



// Websocket test support
type statusHandler struct {}
func (h *statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  Status(w, r)
}
func StatusConnection(token string, rev *Revision) (ws *websocket.Conn, err error) {
  var data []byte
  var dataType int

  // connect to websocket
  s := httptest.NewServer(&statusHandler{})
  wsUrl, _ := url.Parse(s.URL)
  wsUrl.Scheme = "ws"
  if ws, _, err = websocket.DefaultDialer.Dial(wsUrl.String(), nil); err != nil {
    return
  }

  // send authentication
  announce := Announce{ AppToken: token }
  if data, err = json.Marshal(&announce); err != nil {
    return
  }
  ws.WriteMessage(websocket.TextMessage, data)

  // read revision response
  ws.SetReadDeadline(time.Now().Add(TEST_READDEADLINE * time.Second))
  if dataType, data, err = ws.ReadMessage(); err != nil {
    return
  }
  if dataType != websocket.TextMessage {
    err = errors.New("invalid status data type")
    return
  }
  if err = json.Unmarshal(data, rev); err != nil {
    return
  }
  return
}