package zitadel_test

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"gateway/internal/library/zitadel"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCreateHumanUser(t *testing.T) {
	t.Parallel()
	var gotAuth string
	var gotBody map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v2/users/human":
			gotAuth = r.Header.Get("Authorization")
			assert.NoError(t, json.NewDecoder(r.Body).Decode(&gotBody))
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"userId":"zit-123"}`))
		default:
			http.NotFound(w, r)
		}
	}))
	t.Cleanup(srv.Close)

	c, err := zitadel.NewClient(zitadel.Conf{
		Issuer:           srv.URL,
		ServiceUserToken: "pat-test",
		DefaultOrgID:     "org-1",
	})
	require.NoError(t, err)

	res, err := c.CreateHumanUser(context.Background(), zitadel.CreateHumanUserRequest{
		Email:       "alice@example.com",
		Password:    "Secret123!",
		DisplayName: "Alice Smith",
		Language:    "zh-tw",
	})
	require.NoError(t, err)
	require.Equal(t, "zit-123", res.UserID)
	require.Equal(t, "Bearer pat-test", gotAuth)
	require.Equal(t, "alice@example.com", gotBody["username"])
	require.Equal(t, "org-1", gotBody["organizationId"])
}

func TestCreateHumanUserConflict(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "exists", http.StatusConflict)
	}))
	t.Cleanup(srv.Close)

	c, err := zitadel.NewClient(zitadel.Conf{Issuer: srv.URL, ServiceUserToken: testPAT})
	require.NoError(t, err)

	_, err = c.CreateHumanUser(context.Background(), zitadel.CreateHumanUserRequest{
		Email:    "dup@example.com",
		Password: "x",
	})
	require.ErrorIs(t, err, zitadel.ErrUserAlreadyExists)
}

func TestDeactivateUser(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, http.MethodPost, r.Method)
		assert.Equal(t, "/v2/users/u-99/deactivate", r.URL.Path)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"details":{}}`))
	}))
	t.Cleanup(srv.Close)

	c, err := zitadel.NewClient(zitadel.Conf{Issuer: srv.URL, ServiceUserToken: testPAT})
	require.NoError(t, err)
	require.NoError(t, c.DeactivateUser(context.Background(), "u-99"))
}

func TestVerifyPassword(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/oauth/v2/token", r.URL.Path)
		body, _ := io.ReadAll(r.Body)
		vals := parseForm(string(body))
		assert.Equal(t, "password", vals["grant_type"])
		assert.Equal(t, testClientID, vals["client_id"])
		assert.Equal(t, "alice@example.com", vals["username"])
		if vals["password"] != "ok" {
			http.Error(w, "invalid", http.StatusUnauthorized)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"access_token":"at","id_token":"id","expires_in":3600,"token_type":"Bearer"}`))
	}))
	t.Cleanup(srv.Close)

	c, err := zitadel.NewClient(zitadel.Conf{
		Issuer:            srv.URL,
		OAuthClientID:     testClientID,
		OAuthClientSecret: testSecret,
	})
	require.NoError(t, err)

	tok, err := c.VerifyPassword(context.Background(), "alice@example.com", "ok")
	require.NoError(t, err)
	require.Equal(t, "at", tok.AccessToken)
	require.Equal(t, "id", tok.IDToken)

	_, err = c.VerifyPassword(context.Background(), "alice@example.com", "bad")
	require.ErrorIs(t, err, zitadel.ErrInvalidCredentials)
}

func TestVerifyPasswordSession(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v2/sessions":
			_, _ = w.Write([]byte(`{"sessionId":"sess-1"}`))
		case r.Method == http.MethodPatch && r.URL.Path == "/v2/sessions/sess-1":
			_, _ = w.Write([]byte(`{"sessionToken":"tok"}`))
		case r.Method == http.MethodGet && r.URL.Path == "/v2/sessions/sess-1":
			_, _ = w.Write([]byte(`{"session":{"factors":{"user":{"id":"user-42","loginName":"alice@example.com"},"password":{"verifiedAt":"2026-01-01T00:00:00Z"}}}}`))
		default:
			http.NotFound(w, r)
		}
	}))
	t.Cleanup(srv.Close)

	c, err := zitadel.NewClient(zitadel.Conf{Issuer: srv.URL, APIBase: srv.URL, ServiceUserToken: testPAT})
	require.NoError(t, err)

	tok, err := c.VerifyPassword(context.Background(), "alice@example.com", "ok")
	require.NoError(t, err)
	require.Equal(t, "user-42", tok.Subject)
	require.Equal(t, "alice@example.com", tok.Email)
	require.Empty(t, tok.AccessToken)
}

func TestNewClientDisabledWhenIssuerEmpty(t *testing.T) {
	t.Parallel()
	c, err := zitadel.NewClient(zitadel.Conf{})
	require.NoError(t, err)
	require.Nil(t, c)
}

func parseForm(body string) map[string]string {
	vals, err := url.ParseQuery(body)
	if err != nil {
		return map[string]string{}
	}
	out := make(map[string]string, len(vals))
	for k, v := range vals {
		if len(v) > 0 {
			out[k] = v[0]
		}
	}
	return out
}
