package integration

import (
	"fmt"
	"net/http"
	"strings"
	"testing"

	"github.com/contiamo/dev/tests/integration/suite"
	uuid "github.com/satori/go.uuid"
)

type Payload suite.Payload

func TestVirtualDataSourceCreation(t *testing.T) {
	s := suite.New(t)

	var (
		dsID         string
		dsName       = "ds" + strings.Replace(uuid.NewV4().String(), "-", "_", -1)
		foodmartName = "foodmart" + strings.Replace(uuid.NewV4().String(), "-", "_", -1)
		viewName     = "view" + strings.Replace(uuid.NewV4().String(), "-", "_", -1)
		viewTwoName  = "view2" + strings.Replace(uuid.NewV4().String(), "-", "_", -1)
	)

	s.Run("create a virtual datasource through hub", func(s *suite.Suite) {
		path := fmt.Sprintf("/hub/api/v1/%s/datasources", s.ProjectID)
		payload := Payload{
			"name": dsName,
			"type": "virtual",
		}
		resp := s.Post(path, payload)
		defer resp.Body.Close()
		s.Require().Equal(http.StatusCreated, resp.StatusCode)
		createResponse := s.ParsePayload(resp.Body)
		id, ok := createResponse["id"].(string)
		s.Require().True(ok)
		dsID = id
	})

	s.Run("check hub effects", func(s *suite.Suite) {
		s.Run("check if we can get it by id", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/datasources/%s", s.ProjectID, dsID)
			resp := s.Get(path)
			defer resp.Body.Close()
			s.Require().Equal(http.StatusOK, resp.StatusCode)
			data := s.ParsePayload(resp.Body)
			hits := s.Jq(data, `.data.name`)
			s.Require().Len(hits, 1)
			s.Require().Equal(dsName, hits[0])
		})

		s.Run("check if we can get it by name", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/datasources/%s", s.ProjectID, dsName)
			resp := s.Get(path)
			defer resp.Body.Close()
			s.Require().Equal(http.StatusOK, resp.StatusCode)
			data := s.ParsePayload(resp.Body)
			hits := s.Jq(data, `.data.id`)
			s.Require().Len(hits, 1)
			s.Require().Equal(dsID, hits[0])
		})

		s.Run("check if we find it in the datasource list", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/datasources", s.ProjectID)
			resp := s.Get(path)
			defer resp.Body.Close()
			s.Require().Equal(http.StatusOK, resp.StatusCode)
			data := s.ParsePayload(resp.Body)
			hits := s.Jq(data, `.data[] | select(.name == "%s")`, dsName)
			s.Require().Len(hits, 1)
		})
	})

	s.Run("test view creation", func(s *suite.Suite) {
		s.Run("setup foodmart", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/datasources", s.ProjectID)
			payload := Payload{
				"name":           foodmartName,
				"type":           "external",
				"technology":     "hsqldb-foodmart",
				"connectionInfo": Payload{},
			}

			resp := s.Post(path, payload)
			defer resp.Body.Close()
			s.Require().Equal(http.StatusCreated, resp.StatusCode)
		})

		s.Run("setup two views", func(s *suite.Suite) {

			path := fmt.Sprintf("/hub/api/v1/%s/datasources/%s/views", s.ProjectID, dsID)
			resp := s.Post(path, Payload{
				"name": viewName,
				"sql":  fmt.Sprintf("SELECT * FROM %s.ACCOUNT", foodmartName),
			})
			defer resp.Body.Close()
			s.Require().Equal(http.StatusCreated, resp.StatusCode, "path %s", path)

			resp = s.Post(path, Payload{
				"name": viewTwoName,
				"sql":  fmt.Sprintf("SELECT * FROM %s.ACCOUNT", foodmartName),
			})
			defer resp.Body.Close()
			s.Require().Equal(http.StatusCreated, resp.StatusCode, "path %s", path)
		})

		s.Run("check if we can query the view", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/query", s.ProjectID)
			resp := s.Post(path, Payload{
				"sql": fmt.Sprintf(`SELECT * FROM %s.%s`, dsName, viewName),
			})
			defer resp.Body.Close()
			data := s.ParsePayload(resp.Body)
			s.Require().Len(s.Jq(data, `.rows[]`), 11, data)
		})

		s.Run("update view definition", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/datasources/%s/views/%s", s.ProjectID, dsID, viewName)
			resp := s.Put(path, Payload{
				"sql": fmt.Sprintf("SELECT * FROM %s.CATEGORY", foodmartName),
			})
			defer resp.Body.Close()
			s.Require().Equal(http.StatusOK, resp.StatusCode, "path %s", path)
		})
	})

	s.Run("side effects", func(s *suite.Suite) {
		s.Run("check if we can query the updated view", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/query", s.ProjectID)
			resp := s.Post(path, Payload{
				"sql": fmt.Sprintf(`SELECT * FROM %s.%s`, dsName, viewName),
			})
			defer resp.Body.Close()
			data := s.ParsePayload(resp.Body)
			s.Require().Len(s.Jq(data, `.rows[]`), 4, data)
		})

		s.Run("check if we can profile the query", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/profile/sql", s.ProjectID)
			resp := s.Post(path, Payload{
				"projectId": s.ProjectID,
				"sql":       fmt.Sprintf(`SELECT category_id FROM %s.%s`, dsName, viewName),
			})
			defer resp.Body.Close()
			data := s.ParsePayload(resp.Body)
			columns, ok := data["columns"].(map[string]interface{})
			s.Require().True(ok, "unknown response %+v", data)
			s.Require().Len(columns, 1, "unknown response %+v", data)

			profile, ok := columns["category_id"].(map[string]interface{})
			s.Require().True(ok, "unknown response %+v", columns)
			s.Require().Equal(profile["dataType"], "string", "unknown response %+v", profile)
		})
	})

	s.Run("delete the view", func(s *suite.Suite) {
		path := fmt.Sprintf("/hub/api/v1/%s/datasources/%s/tables/%s", s.ProjectID, dsID, viewName)
		resp := s.Delete(path)
		defer resp.Body.Close()
		s.Require().Equal(http.StatusNoContent, resp.StatusCode)

		s.Run("check that we can't query the deleted view", func(s *suite.Suite) {
			path := fmt.Sprintf("/hub/api/v1/%s/query", s.ProjectID)
			resp := s.Post(path, Payload{
				"sql": fmt.Sprintf(`SELECT * FROM %s.%s`, dsName, viewName),
			})
			defer resp.Body.Close()
			s.Require().Equal(http.StatusUnprocessableEntity, resp.StatusCode, s.ParsePayload(resp.Body))
		})
	})

	s.Run("delete the virtual datasource", func(s *suite.Suite) {
		path := fmt.Sprintf("/hub/api/v1/%s/datasources/%s", s.ProjectID, dsID)
		resp := s.Delete(path)
		defer resp.Body.Close()
		s.Require().Equal(http.StatusNoContent, resp.StatusCode)
	})

}
