package service

import (
	"context"
	"encoding/json"
	"net/http"
	"testing"

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

func TestKetoService_CheckPermission(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/relation-tuples/check", r.URL.Path)
		assert.Equal(t, "user1", r.URL.Query().Get("subject_id"))
		assert.Equal(t, "tenants", r.URL.Query().Get("namespace"))
		assert.Equal(t, "tenant1", r.URL.Query().Get("object"))
		assert.Equal(t, "admin", r.URL.Query().Get("relation"))

		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(checkResponse{Allowed: true})
	})

	s := &ketoService{
		readURL: "http://keto-read.local",
		client:  clientForHandler(handler),
	}

	allowed, err := s.CheckPermission(context.Background(), "user1", "tenants", "tenant1", "admin")
	assert.NoError(t, err)
	assert.True(t, allowed)
}

func TestKetoService_CreateRelation(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/admin/relation-tuples", r.URL.Path)
		assert.Equal(t, "PUT", r.Method)

		var body map[string]interface{}
		_ = json.NewDecoder(r.Body).Decode(&body)
		assert.Equal(t, "tenants", body["namespace"])
		assert.Equal(t, "tenant1", body["object"])
		assert.Equal(t, "admin", body["relation"])
		assert.Equal(t, "user1", body["subject_id"])

		w.WriteHeader(http.StatusCreated)
	})

	s := &ketoService{
		writeURL: "http://keto-write.local",
		client:   clientForHandler(handler),
	}

	err := s.CreateRelation(context.Background(), "tenants", "tenant1", "admin", "user1")
	assert.NoError(t, err)
}

func TestKetoService_DeleteRelation(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/admin/relation-tuples", r.URL.Path)
		assert.Equal(t, "DELETE", r.Method)
		assert.Equal(t, "user1", r.URL.Query().Get("subject_id"))
		assert.Equal(t, "tenants", r.URL.Query().Get("namespace"))
		assert.Equal(t, "tenant1", r.URL.Query().Get("object"))
		assert.Equal(t, "admin", r.URL.Query().Get("relation"))

		w.WriteHeader(http.StatusNoContent)
	})

	s := &ketoService{
		writeURL: "http://keto-write.local",
		client:   clientForHandler(handler),
	}

	err := s.DeleteRelation(context.Background(), "tenants", "tenant1", "admin", "user1")
	assert.NoError(t, err)
}

func TestKetoService_ListRelations(t *testing.T) {
	tuples := []RelationTuple{
		{Namespace: "tenants", Object: "tenant1", Relation: "admin", SubjectID: "user1"},
	}

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/relation-tuples", r.URL.Path)

		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(relationTuplesResponse{RelationTuples: tuples})
	})

	s := &ketoService{
		readURL: "http://keto-read.local",
		client:  clientForHandler(handler),
	}

	result, err := s.ListRelations(context.Background(), "tenants", "tenant1", "admin", "user1")
	assert.NoError(t, err)
	assert.Equal(t, tuples, result)
}

func TestKetoService_ErrorHandling(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("internal error"))
	})

	s := &ketoService{
		readURL:  "http://keto-read.local",
		writeURL: "http://keto-write.local",
		client:   clientForHandler(handler),
	}

	_, err := s.CheckPermission(context.Background(), "u", "n", "o", "r")
	assert.Error(t, err)

	err = s.DeleteRelation(context.Background(), "n", "o", "r", "s")
	assert.Error(t, err)

	_, err = s.ListRelations(context.Background(), "n", "o", "r", "s")
	assert.Error(t, err)
}

func TestKetoService_CheckPermission_Forbidden(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusForbidden)
	})

	s := &ketoService{
		readURL: "http://keto-read.local",
		client:  clientForHandler(handler),
	}
	allowed, err := s.CheckPermission(context.Background(), "u", "n", "o", "r")
	assert.NoError(t, err)
	assert.False(t, allowed)
}

func TestKetoService_CreateRelation_Retry(t *testing.T) {
	attempts := 0
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		attempts++
		if attempts < 2 {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	s := &ketoService{
		writeURL: "http://keto-write.local",
		client:   clientForHandler(handler),
	}

	err := s.CreateRelation(context.Background(), "n", "o", "r", "s")
	assert.NoError(t, err)
	assert.Equal(t, 2, attempts)
}
