// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package atom

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

func TestBootstrapMagistralaActionsCreatesMissingActionsAndApplicability(t *testing.T) {
	actions := map[string]Capability{
		atomActionRead:   {ID: "read-id", Name: atomActionRead},
		atomActionWrite:  {ID: "write-id", Name: atomActionWrite},
		atomActionDelete: {ID: "delete-id", Name: atomActionDelete},
		atomActionManage: {ID: "manage-id", Name: atomActionManage},
	}
	var applicability []map[string]any
	var assignmentRules []map[string]any

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost || r.URL.Path != atomGraphQLPath {
			t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
		}
		var payload struct {
			Query     string         `json:"query"`
			Variables map[string]any `json:"variables"`
		}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			t.Fatalf("decode request: %v", err)
		}

		switch {
		case strings.Contains(payload.Query, "query Actions"):
			items := make([]Capability, 0, len(actions))
			for _, action := range actions {
				items = append(items, action)
			}
			_ = json.NewEncoder(w).Encode(map[string]any{
				"data": map[string]any{
					"actions": map[string]any{"items": items, "total": len(items)},
				},
			})
		case strings.Contains(payload.Query, "query ActionAssignmentRules"):
			_ = json.NewEncoder(w).Encode(map[string]any{
				"data": map[string]any{
					"actionAssignmentRules": map[string]any{"items": []map[string]any{}, "total": 0},
				},
			})
		case strings.Contains(payload.Query, "createActionAssignmentRule"):
			input := payload.Variables["input"].(map[string]any)
			assignmentRules = append(assignmentRules, input)
			_ = json.NewEncoder(w).Encode(map[string]any{
				"data": map[string]any{
					"createActionAssignmentRule": map[string]any{
						"id":          input["actionName"].(string) + "-rule-id",
						"tenant_id":   "",
						"entity_kind": input["entityKind"],
						"action_name": input["actionName"],
						"object_kind": input["objectKind"],
						"object_type": input["objectType"],
						"decision":    input["decision"],
						"is_absolute": input["isAbsolute"],
						"created_at":  "2026-06-18T00:00:00Z",
					},
				},
			})
		case strings.Contains(payload.Query, "createAction"):
			input := payload.Variables["input"].(map[string]any)
			name := input["name"].(string)
			action := Capability{ID: name + "-id", Name: name}
			actions[name] = action
			_ = json.NewEncoder(w).Encode(map[string]any{
				"data": map[string]any{"createAction": action},
			})
		case strings.Contains(payload.Query, "addActionApplicability"):
			input := payload.Variables["input"].(map[string]any)
			applicability = append(applicability, input)
			_ = json.NewEncoder(w).Encode(map[string]any{
				"data": map[string]any{
					"addActionApplicability": map[string]any{
						"action_id":   input["actionId"],
						"action_name": "action",
						"object_kind": input["objectKind"],
						"object_type": input["objectType"],
						"description": "",
					},
				},
			})
		default:
			t.Fatalf("unexpected GraphQL payload: %s", payload.Query)
		}
	}))
	defer srv.Close()

	client := NewClient(Config{URL: srv.URL, Timeout: time.Second})
	if err := BootstrapMagistralaActions(context.Background(), client); err != nil {
		t.Fatalf("bootstrap failed: %v", err)
	}

	for _, name := range []string{atomActionRead, atomActionWrite, atomActionDelete, atomActionManage, atomActionPublish, atomActionSubscribe, atomActionExecute, atomActionList, atomActionAlarmRead, atomActionAlarmUpdate, atomActionAlarmDelete, atomActionAlarmAssign, atomActionAlarmAcknowledge, atomActionAlarmResolve} {
		if _, ok := actions[name]; !ok {
			t.Fatalf("action %q was not ensured", name)
		}
	}
	if len(applicability) != len(magistralaActionApplicability) {
		t.Fatalf("unexpected applicability count: got %d want %d", len(applicability), len(magistralaActionApplicability))
	}
	assertApplicability(t, applicability, "write-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "alarm_read-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "alarm_update-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "alarm_delete-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "alarm_assign-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "alarm_acknowledge-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "alarm_resolve-id", atomObjectKindTenant, "")
	assertApplicability(t, applicability, "read-id", atomObjectKindGroup, "")
	assertApplicability(t, applicability, "write-id", atomObjectKindGroup, "")
	assertApplicability(t, applicability, "delete-id", atomObjectKindGroup, "")
	assertApplicability(t, applicability, "manage-id", atomObjectKindGroup, "")
	assertApplicability(t, applicability, "list-id", atomObjectKindGroup, "")
	assertApplicability(t, applicability, "publish-id", atomObjectKindResource, "resource:channel")
	assertApplicability(t, applicability, "execute-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "list-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "alarm_read-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "alarm_update-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "alarm_delete-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "alarm_assign-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "alarm_acknowledge-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "alarm_resolve-id", atomObjectKindResource, "resource:rule")
	assertApplicability(t, applicability, "execute-id", atomObjectKindResource, "resource:report")
	assertApplicability(t, applicability, "list-id", atomObjectKindResource, "resource:report")
	if len(assignmentRules) != len(magistralaActionAssignmentRules) {
		t.Fatalf("unexpected assignment guardrail count: got %d want %d", len(assignmentRules), len(magistralaActionAssignmentRules))
	}
	assertAssignmentRule(t, assignmentRules, atomKindDevice, atomActionPublish, atomObjectKindResource, "resource:channel", "allow")
	assertAssignmentRule(t, assignmentRules, atomKindDevice, atomActionSubscribe, atomObjectKindResource, "resource:channel", "allow")
}

func assertApplicability(t *testing.T, entries []map[string]any, actionID, objectKind, objectType string) {
	t.Helper()
	for _, entry := range entries {
		entryObjectType, _ := entry["objectType"].(string)
		if entry["actionId"] == actionID && entry["objectKind"] == objectKind && entryObjectType == objectType {
			return
		}
	}
	t.Fatalf("missing applicability action=%s object=%s:%s", actionID, objectKind, objectType)
}

func assertAssignmentRule(t *testing.T, entries []map[string]any, entityKind, actionName, objectKind, objectType, decision string) {
	t.Helper()
	for _, entry := range entries {
		if entry["entityKind"] == entityKind &&
			entry["actionName"] == actionName &&
			entry["objectKind"] == objectKind &&
			entry["objectType"] == objectType &&
			entry["decision"] == decision &&
			entry["isAbsolute"] == false {
			return
		}
	}
	t.Fatalf("missing assignment guardrail entity=%s action=%s object=%s:%s decision=%s", entityKind, actionName, objectKind, objectType, decision)
}
