package suite

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"

	"github.com/sirupsen/logrus"
)

type meData struct {
	ID           string       `json:"id"`
	Name         string       `json:"name"`
	Email        string       `json:"email"`
	Realms       []realm      `json:"realms"`
	Organization organization `json:"organization"`
}

type organization struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug"`
}

type realm struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	TenantID string `json:"tenantId"`
}

var csrfRegexp = regexp.MustCompile(`name="csrf" value="(.+)"`)

// Login authenticates the test suite with the provided user. This user session will be used
// throughout the tests until you call s.Login again.
func (s *Suite) Login(email, password, tenant string) error {
	loginURL := s.PlatformURL("/auth/login/" + s.Config.TenantSlug)
	req, err := http.NewRequest(http.MethodGet, loginURL, nil)
	if err != nil {
		return err
	}

	resp, err := s.Do(req)
	if err != nil {
		return fmt.Errorf("login page GET request to %q failed: %w", loginURL, err)
	}
	defer resp.Body.Close()
	buf := &bytes.Buffer{}
	_, err = io.Copy(buf, resp.Body)
	if err != nil {
		return fmt.Errorf("can not copy response body: %w", err)
	}
	matches := csrfRegexp.FindSubmatch(buf.Bytes())
	if len(matches) != 2 {
		return fmt.Errorf("can not find csrf token in login page from %q", loginURL)
	}
	token := string(matches[1])
	values := url.Values{
		"email":    {email},
		"password": {password},
		"csrf":     {token},
	}
	if tenant != "" {
		values["tenant"] = []string{tenant}
	}

	req, err = http.NewRequest(http.MethodPost, loginURL, strings.NewReader(values.Encode()))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "text/html")
	resp, err = s.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Println(fmt.Errorf("error reading login body: %w", err))
		}
		return fmt.Errorf("login to %q failed with status code: %d\n%s", loginURL, resp.StatusCode, body)
	}

	u, err := url.Parse(s.Config.BaseURL + "/")
	if err != nil {
		return err
	}

	cookies := s.Client.Jar.Cookies(u)
	for _, cookie := range cookies {
		if cookie.Name == "double-cookie" {
			s.doubleCookie = cookie.Value
			s.currentUser = email
		}
	}
	req, err = http.NewRequest(http.MethodGet, s.PlatformURL("/auth/api/v3/me"), nil)
	if err != nil {
		return err
	}

	resp, err = s.Do(req)
	if err != nil {
		return fmt.Errorf("me request failed: %w", err)
	}
	defer resp.Body.Close()

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("can not read me response: %w", err)
	}

	body := bytes.NewReader(b)

	data := meData{}
	if e := json.NewDecoder(body).Decode(&data); e != nil {
		_, err := body.Seek(0, io.SeekStart)
		if err != nil {
			logrus.Warn(err)
		}
		_, err = io.Copy(os.Stdout, body)
		if err != nil {
			logrus.Warn(err)
		}
		return fmt.Errorf("can not parse me response: %w", e)
	}
	s.TenantID = data.Organization.ID
	s.UserIDs[email] = data.ID

	return nil
}
