package console

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"testing"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"golang.org/x/net/html"

	"gitcode.com/openFuyao/e2e-auto-test/e2e/framework/env"
)

var (
	baseURL string
	client  *http.Client
)

var _ = BeforeSuite(func() {
	// Load .env file
	err := env.LoadEnv(".env")
	if err != nil {
		Fail("Error loading .env file: " + err.Error())
	}

	baseURL = os.Getenv("TEST_NODE2_IP")
	if baseURL == "" {
		Fail("TEST_NODE2_IP is not set")
	}
	baseURL = "https://" + baseURL + ":31616"

	// Create cookie jar for session management
	jar, err := cookiejar.New(nil)
	if err != nil {
		Fail("Error creating cookie jar: " + err.Error())
	}

	client = &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse // handle every redirection
		},
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true, // Skip TLS certificate verification
			},
		},
		Timeout: 30 * time.Second,
		Jar:     jar,
	}

	// Initialize test configuration for makeRequest function
	testConfig = &ConsoleTestConfig{
		BaseURL: baseURL,
		Client:  client,
	}

	// Perform login authentication
	performLogin()
})

var _ = AfterSuite(func() {
	performLogout()
})

func performLogout() {
	if client == nil {
		return
	}

	logoutURLStr := baseURL + "/rest/auth/logout"
	resp, err := client.Post(logoutURLStr, "application/json", nil)
	Expect(err).NotTo(HaveOccurred())
	defer resp.Body.Close()
	Expect(resp.StatusCode).To(Equal(http.StatusNoContent))
}

// extractLoginFormData extract CSRF token & then from HTML
func extractLoginFormData(htmlContent string) (csrfToken, thenValue string, err error) {
	doc, err := html.Parse(strings.NewReader(htmlContent))
	Expect(err).NotTo(HaveOccurred())

	var findInputs func(*html.Node)
	findInputs = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "input" {
			var name, value string
			for _, attr := range n.Attr {
				switch attr.Key {
				case "name":
					name = attr.Val
				case "value":
					value = attr.Val
				}
			}

			switch name {
			case "gorilla.csrf.Token":
				csrfToken = value
			case "then":
				thenValue = value
			}
		}

		for c := n.FirstChild; c != nil; c = c.NextSibling {
			findInputs(c)
		}
	}

	findInputs(doc)

	if csrfToken == "" {
		return "", "", fmt.Errorf("CSRF token not found in HTML")
	}
	if thenValue == "" {
		return "", "", fmt.Errorf("then parameter not found in HTML")
	}

	return csrfToken, thenValue, nil
}

// performLogin performs the login authentication and saves cookies
func performLogin() {
	loginPageURLStr := baseURL + "/rest/auth/login"
	resp, err := client.Get(loginPageURLStr)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusFound))
	location := resp.Header.Get("Location")
	Expect(location).To(HavePrefix("/oauth2/oauth/authorize"))

	resp, err = client.Get(baseURL + location)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusFound))
	location = resp.Header.Get("Location")
	Expect(location).To(HavePrefix("/oauth2/auth/login/fuyaoPasswordProvider"))

	resp, err = client.Get(baseURL + location)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusOK))
	body, err := io.ReadAll(resp.Body)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()

	// Parse HTML response to extract CSRF token and then parameter
	htmlContent := string(body)
	csrfToken, thenValue, err := extractLoginFormData(htmlContent)
	Expect(err).NotTo(HaveOccurred(), "failed to extract login form data: %v", err)

	// Convert password string to int array (each char as int)
	passwordStr := os.Getenv("TEST_PASSWORD")
	passwordInts := make([]int, len(passwordStr))
	for i, char := range passwordStr {
		passwordInts[i] = int(char)
	}

	jsonData, err := json.Marshal(map[string]interface{}{
		"username": os.Getenv("TEST_USERNAME"),
		"password": passwordInts,
		"then":     thenValue,
	})
	Expect(err).NotTo(HaveOccurred(), "failed to marshal login data: %v", err)

	loginURLStr := baseURL + "/oauth2/auth/login/fuyaoPasswordProvider"
	loginURL, err := url.Parse(loginURLStr)
	Expect(err).NotTo(HaveOccurred(), "failed to parse login URL: %v", err)

	// Create login request
	loginReq := &http.Request{
		Method: "POST",
		URL:    loginURL,
		Header: http.Header{
			"Content-Type": {"application/json"},
			"X-CSRF-Token": {csrfToken},
			"Referer":      {location},
		},
		Body: io.NopCloser(bytes.NewReader(jsonData)),
	}
	resp, err = client.Do(loginReq)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusFound))
	location = resp.Header.Get("Location")
	Expect(location).To(HavePrefix("/oauth2/oauth/authorize"))

	// GET /oauth2/oauth/authorize - should redirect to callback
	resp, err = client.Get(baseURL + location)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusFound))
	location = resp.Header.Get("Location")
	Expect(location).To(HavePrefix("/rest/auth/callback"))

	resp, err = client.Get(baseURL + location)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusFound))
	location = resp.Header.Get("Location")
	Expect(location).To(Equal("/"))

	resp, err = client.Get(baseURL + location)
	Expect(err).NotTo(HaveOccurred())
	Expect(resp.StatusCode).To(Equal(http.StatusOK))
	body, err = io.ReadAll(resp.Body)
	Expect(err).NotTo(HaveOccurred())
	Expect(string(body)).To(ContainSubstring("<div id=\"root\"></div>"))
	resp.Body.Close()
}

func TestConsoleTest(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "ConsoleTest Suite")
}