package service

import (
	"baron-sso-backend/internal/domain"
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
)

// clientForHandler returns an http.Client that routes requests to the given handler
// without real network sockets.
func clientForHandler(h http.Handler) *http.Client {
	return &http.Client{
		Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
			// Clone request body for handler
			var bodyBytes []byte
			if req.Body != nil {
				bodyBytes, _ = io.ReadAll(req.Body)
			}
			r := httptest.NewRequest(req.Method, req.URL.String(), bytes.NewReader(bodyBytes))
			r.Header = req.Header.Clone()

			w := httptest.NewRecorder()
			h.ServeHTTP(w, r)
			return w.Result(), nil
		}),
	}
}

type roundTripperFunc func(req *http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }

func TestUpdateUserPassword_Success(t *testing.T) {
	const (
		loginID     = "user@example.com"
		identityID  = "7f0dc8c3-9d5d-4f57-b3d1-123456789abc"
		newPassword = "Sup3rStr0ng!Pass#2026"
	)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasPrefix(r.URL.Path, "/admin/identities") && r.Method == http.MethodGet:
			if r.URL.Path == "/admin/identities" {
				q := r.URL.Query()
				if got := q.Get("credentials_identifier"); got != loginID {
					t.Fatalf("expected credentials_identifier=%s, got=%s", loginID, got)
				}
				_ = json.NewEncoder(w).Encode([]map[string]any{
					{
						"id": identityID,
						"traits": map[string]any{
							"email": loginID,
						},
					},
				})
				return
			}
			if r.URL.Path != "/admin/identities/"+identityID {
				t.Fatalf("unexpected identity lookup path: %s", r.URL.Path)
			}
			_ = json.NewEncoder(w).Encode(map[string]any{
				"id":        identityID,
				"schema_id": "default",
				"state":     "active",
				"traits": map[string]any{
					"email": loginID,
				},
			})
			return
		case r.URL.Path == "/admin/identities/"+identityID && r.Method == http.MethodPut:
			body, _ := io.ReadAll(r.Body)
			if !strings.Contains(string(body), "\"hashed_password\"") {
				t.Fatalf("payload missing hashed_password, body=%s", string(body))
			}
			if strings.Contains(string(body), newPassword) {
				t.Fatalf("payload must not contain plain password, body=%s", string(body))
			}
			if !strings.Contains(string(body), "\"schema_id\":\"default\"") {
				t.Fatalf("payload missing schema_id, body=%s", string(body))
			}
			w.WriteHeader(http.StatusOK)
			return
		default:
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
		}
	})

	provider := &OryProvider{
		KratosAdminURL: "http://kratos-admin.local",
		HTTPClient:     clientForHandler(handler),
	}

	if err := provider.UpdateUserPassword(loginID, newPassword, nil); err != nil {
		t.Fatalf("UpdateUserPassword returned error: %v", err)
	}
}

func TestUpdateUserPassword_NotFound(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.HasPrefix(r.URL.Path, "/admin/identities") && r.Method == http.MethodGet {
			http.NotFound(w, r)
			return
		}
		t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
	})

	provider := &OryProvider{
		KratosAdminURL: "http://kratos-admin.local",
		HTTPClient:     clientForHandler(handler),
	}

	err := provider.UpdateUserPassword("user@example.com", "Sup3rStr0ng!Pass#2026", nil)
	if err == nil || !strings.Contains(err.Error(), "identity not found") {
		t.Fatalf("expected identity not found error, got: %v", err)
	}
}

func TestUpdateUserPassword_ServerError(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasPrefix(r.URL.Path, "/admin/identities") && r.Method == http.MethodGet:
			if r.URL.Path == "/admin/identities" {
				_ = json.NewEncoder(w).Encode([]map[string]any{
					{
						"id": "abc",
						"traits": map[string]any{
							"email": "user@example.com",
						},
					},
				})
				return
			}
			if r.URL.Path == "/admin/identities/abc" {
				_ = json.NewEncoder(w).Encode(map[string]any{
					"id":        "abc",
					"schema_id": "default",
					"state":     "active",
					"traits": map[string]any{
						"email": "user@example.com",
					},
				})
				return
			}
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
		case r.URL.Path == "/admin/identities/abc" && r.Method == http.MethodPut:
			http.Error(w, "boom", http.StatusInternalServerError)
			return
		default:
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
		}
	})

	provider := &OryProvider{
		KratosAdminURL: "http://kratos-admin.local",
		HTTPClient:     clientForHandler(handler),
	}

	err := provider.UpdateUserPassword("user@example.com", "Sup3rStr0ng!Pass#2026", nil)
	if err == nil || !strings.Contains(err.Error(), "password update failed") {
		t.Fatalf("expected server error, got: %v", err)
	}
}

func TestFindIdentityID_QueryEncoding(t *testing.T) {
	loginID := "user+alias@example.com"
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		values, _ := url.ParseQuery(r.URL.RawQuery)
		if values.Get("credentials_identifier") != loginID {
			t.Fatalf("expected credentials_identifier=%s, got=%s", loginID, values.Get("credentials_identifier"))
		}
		_ = json.NewEncoder(w).Encode([]map[string]any{
			{
				"id": "id-123",
				"traits": map[string]any{
					"email": loginID,
				},
			},
		})
	})

	provider := &OryProvider{
		KratosAdminURL: "http://kratos-admin.local",
		HTTPClient:     clientForHandler(handler),
	}

	id, err := provider.findIdentityID(loginID)
	if err != nil {
		t.Fatalf("findIdentityID returned error: %v", err)
	}
	if id != "id-123" {
		t.Fatalf("expected id-123, got %s", id)
	}
}

func TestOryProvider_CreateUser_RejectsRequestedIdentityID(t *testing.T) {
	const (
		email      = "newuser@test.com"
		name       = "New User"
		customUuid = "550e8400-e29b-41d4-a716-446655440000"
		password   = "secret123456"
	)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
	})

	provider := &OryProvider{
		KratosAdminURL: "http://kratos-admin.local",
		HTTPClient:     clientForHandler(handler),
	}

	id, err := provider.CreateUser(&domain.BrokerUser{
		ID:    customUuid,
		Email: email,
		Name:  name,
	}, password)
	if err == nil || !strings.Contains(err.Error(), "requested identity id import is disabled") {
		t.Fatalf("expected requested identity id rejection, got id=%s err=%v", id, err)
	}
}
