package db

import (
	"errors"
	"testing"
	"time"

	"git.wntrmute.dev/mc/mcias/internal/model"
)

func TestCreateAndGetPolicyRule(t *testing.T) {
	db := openTestDB(t)

	ruleJSON := `{"actions":["pgcreds:read"],"resource_type":"pgcreds","effect":"allow"}`
	rec, err := db.CreatePolicyRule("test rule", 50, ruleJSON, nil, nil, nil)
	if err != nil {
		t.Fatalf("CreatePolicyRule: %v", err)
	}
	if rec.ID == 0 {
		t.Error("expected non-zero ID after create")
	}
	if rec.Priority != 50 {
		t.Errorf("expected priority 50, got %d", rec.Priority)
	}
	if !rec.Enabled {
		t.Error("new rule should be enabled by default")
	}

	got, err := db.GetPolicyRule(rec.ID)
	if err != nil {
		t.Fatalf("GetPolicyRule: %v", err)
	}
	if got.Description != "test rule" {
		t.Errorf("expected description %q, got %q", "test rule", got.Description)
	}
	if got.RuleJSON != ruleJSON {
		t.Errorf("rule_json mismatch: got %q", got.RuleJSON)
	}
}

func TestGetPolicyRule_NotFound(t *testing.T) {
	db := openTestDB(t)

	_, err := db.GetPolicyRule(99999)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound, got %v", err)
	}
}

func TestListPolicyRules(t *testing.T) {
	db := openTestDB(t)

	_, _ = db.CreatePolicyRule("rule A", 100, `{"effect":"allow"}`, nil, nil, nil)
	_, _ = db.CreatePolicyRule("rule B", 50, `{"effect":"deny"}`, nil, nil, nil)
	_, _ = db.CreatePolicyRule("rule C", 200, `{"effect":"allow"}`, nil, nil, nil)

	rules, err := db.ListPolicyRules(false)
	if err != nil {
		t.Fatalf("ListPolicyRules: %v", err)
	}
	if len(rules) != 3 {
		t.Fatalf("expected 3 rules, got %d", len(rules))
	}
	// Should be ordered by priority ascending.
	if rules[0].Priority > rules[1].Priority || rules[1].Priority > rules[2].Priority {
		t.Errorf("rules not sorted by priority: %v %v %v",
			rules[0].Priority, rules[1].Priority, rules[2].Priority)
	}
}

func TestListPolicyRules_EnabledOnly(t *testing.T) {
	db := openTestDB(t)

	r1, _ := db.CreatePolicyRule("enabled rule", 100, `{"effect":"allow"}`, nil, nil, nil)
	r2, _ := db.CreatePolicyRule("disabled rule", 100, `{"effect":"deny"}`, nil, nil, nil)

	if err := db.SetPolicyRuleEnabled(r2.ID, false); err != nil {
		t.Fatalf("SetPolicyRuleEnabled: %v", err)
	}

	all, err := db.ListPolicyRules(false)
	if err != nil {
		t.Fatalf("ListPolicyRules(all): %v", err)
	}
	if len(all) != 2 {
		t.Errorf("expected 2 total rules, got %d", len(all))
	}

	enabled, err := db.ListPolicyRules(true)
	if err != nil {
		t.Fatalf("ListPolicyRules(enabledOnly): %v", err)
	}
	if len(enabled) != 1 {
		t.Fatalf("expected 1 enabled rule, got %d", len(enabled))
	}
	if enabled[0].ID != r1.ID {
		t.Errorf("wrong rule returned: got ID %d, want %d", enabled[0].ID, r1.ID)
	}
}

func TestUpdatePolicyRule(t *testing.T) {
	db := openTestDB(t)

	rec, _ := db.CreatePolicyRule("original", 100, `{"effect":"allow"}`, nil, nil, nil)

	newDesc := "updated description"
	newPriority := 25
	if err := db.UpdatePolicyRule(rec.ID, &newDesc, &newPriority, nil, nil, nil); err != nil {
		t.Fatalf("UpdatePolicyRule: %v", err)
	}

	got, err := db.GetPolicyRule(rec.ID)
	if err != nil {
		t.Fatalf("GetPolicyRule after update: %v", err)
	}
	if got.Description != newDesc {
		t.Errorf("expected description %q, got %q", newDesc, got.Description)
	}
	if got.Priority != newPriority {
		t.Errorf("expected priority %d, got %d", newPriority, got.Priority)
	}
	// RuleJSON should be unchanged.
	if got.RuleJSON != `{"effect":"allow"}` {
		t.Errorf("rule_json should not change when not provided: %q", got.RuleJSON)
	}
}

func TestUpdatePolicyRule_RuleJSON(t *testing.T) {
	db := openTestDB(t)

	rec, _ := db.CreatePolicyRule("rule", 100, `{"effect":"allow"}`, nil, nil, nil)

	newJSON := `{"effect":"deny","roles":["auditor"]}`
	if err := db.UpdatePolicyRule(rec.ID, nil, nil, &newJSON, nil, nil); err != nil {
		t.Fatalf("UpdatePolicyRule (json only): %v", err)
	}

	got, err := db.GetPolicyRule(rec.ID)
	if err != nil {
		t.Fatalf("GetPolicyRule: %v", err)
	}
	if got.RuleJSON != newJSON {
		t.Errorf("expected updated rule_json, got %q", got.RuleJSON)
	}
	// Description and priority unchanged.
	if got.Description != "rule" {
		t.Errorf("description should be unchanged, got %q", got.Description)
	}
}

func TestSetPolicyRuleEnabled(t *testing.T) {
	db := openTestDB(t)

	rec, _ := db.CreatePolicyRule("toggle rule", 100, `{"effect":"allow"}`, nil, nil, nil)
	if !rec.Enabled {
		t.Fatal("new rule should be enabled")
	}

	if err := db.SetPolicyRuleEnabled(rec.ID, false); err != nil {
		t.Fatalf("SetPolicyRuleEnabled(false): %v", err)
	}
	got, _ := db.GetPolicyRule(rec.ID)
	if got.Enabled {
		t.Error("rule should be disabled after SetPolicyRuleEnabled(false)")
	}

	if err := db.SetPolicyRuleEnabled(rec.ID, true); err != nil {
		t.Fatalf("SetPolicyRuleEnabled(true): %v", err)
	}
	got, _ = db.GetPolicyRule(rec.ID)
	if !got.Enabled {
		t.Error("rule should be enabled after SetPolicyRuleEnabled(true)")
	}
}

func TestDeletePolicyRule(t *testing.T) {
	db := openTestDB(t)

	rec, _ := db.CreatePolicyRule("to delete", 100, `{"effect":"allow"}`, nil, nil, nil)

	if err := db.DeletePolicyRule(rec.ID); err != nil {
		t.Fatalf("DeletePolicyRule: %v", err)
	}

	_, err := db.GetPolicyRule(rec.ID)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound after delete, got %v", err)
	}
}

func TestDeletePolicyRule_NonExistent(t *testing.T) {
	db := openTestDB(t)

	// Deleting a non-existent rule should be a no-op, not an error.
	if err := db.DeletePolicyRule(99999); err != nil {
		t.Errorf("DeletePolicyRule on nonexistent ID should not error: %v", err)
	}
}

func TestCreatePolicyRule_WithCreatedBy(t *testing.T) {
	db := openTestDB(t)

	acct, _ := db.CreateAccount("policy-creator", model.AccountTypeHuman, "hash")
	rec, err := db.CreatePolicyRule("by user", 100, `{"effect":"allow"}`, &acct.ID, nil, nil)
	if err != nil {
		t.Fatalf("CreatePolicyRule with createdBy: %v", err)
	}

	got, _ := db.GetPolicyRule(rec.ID)
	if got.CreatedBy == nil || *got.CreatedBy != acct.ID {
		t.Errorf("expected CreatedBy=%d, got %v", acct.ID, got.CreatedBy)
	}
}

func TestCreatePolicyRule_WithExpiresAt(t *testing.T) {
	db := openTestDB(t)

	exp := time.Date(2030, 6, 1, 0, 0, 0, 0, time.UTC)
	rec, err := db.CreatePolicyRule("expiring rule", 100, `{"effect":"allow"}`, nil, nil, &exp)
	if err != nil {
		t.Fatalf("CreatePolicyRule with expiresAt: %v", err)
	}

	got, err := db.GetPolicyRule(rec.ID)
	if err != nil {
		t.Fatalf("GetPolicyRule: %v", err)
	}
	if got.ExpiresAt == nil {
		t.Fatal("expected ExpiresAt to be set")
	}
	if !got.ExpiresAt.Equal(exp) {
		t.Errorf("expected ExpiresAt=%v, got %v", exp, *got.ExpiresAt)
	}
	if got.NotBefore != nil {
		t.Errorf("expected NotBefore=nil, got %v", *got.NotBefore)
	}
}

func TestCreatePolicyRule_WithNotBefore(t *testing.T) {
	db := openTestDB(t)

	nb := time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
	rec, err := db.CreatePolicyRule("scheduled rule", 100, `{"effect":"allow"}`, nil, &nb, nil)
	if err != nil {
		t.Fatalf("CreatePolicyRule with notBefore: %v", err)
	}

	got, err := db.GetPolicyRule(rec.ID)
	if err != nil {
		t.Fatalf("GetPolicyRule: %v", err)
	}
	if got.NotBefore == nil {
		t.Fatal("expected NotBefore to be set")
	}
	if !got.NotBefore.Equal(nb) {
		t.Errorf("expected NotBefore=%v, got %v", nb, *got.NotBefore)
	}
	if got.ExpiresAt != nil {
		t.Errorf("expected ExpiresAt=nil, got %v", *got.ExpiresAt)
	}
}

func TestCreatePolicyRule_WithBothTimes(t *testing.T) {
	db := openTestDB(t)

	nb := time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
	exp := time.Date(2030, 6, 1, 0, 0, 0, 0, time.UTC)
	rec, err := db.CreatePolicyRule("windowed rule", 100, `{"effect":"allow"}`, nil, &nb, &exp)
	if err != nil {
		t.Fatalf("CreatePolicyRule with both times: %v", err)
	}

	got, err := db.GetPolicyRule(rec.ID)
	if err != nil {
		t.Fatalf("GetPolicyRule: %v", err)
	}
	if got.NotBefore == nil || !got.NotBefore.Equal(nb) {
		t.Errorf("NotBefore mismatch: got %v", got.NotBefore)
	}
	if got.ExpiresAt == nil || !got.ExpiresAt.Equal(exp) {
		t.Errorf("ExpiresAt mismatch: got %v", got.ExpiresAt)
	}
}

func TestUpdatePolicyRule_SetExpiresAt(t *testing.T) {
	db := openTestDB(t)

	rec, _ := db.CreatePolicyRule("no expiry", 100, `{"effect":"allow"}`, nil, nil, nil)

	exp := time.Date(2030, 12, 31, 23, 59, 59, 0, time.UTC)
	expPtr := &exp
	if err := db.UpdatePolicyRule(rec.ID, nil, nil, nil, nil, &expPtr); err != nil {
		t.Fatalf("UpdatePolicyRule (set expires_at): %v", err)
	}

	got, _ := db.GetPolicyRule(rec.ID)
	if got.ExpiresAt == nil {
		t.Fatal("expected ExpiresAt to be set after update")
	}
	if !got.ExpiresAt.Equal(exp) {
		t.Errorf("expected ExpiresAt=%v, got %v", exp, *got.ExpiresAt)
	}
}

func TestUpdatePolicyRule_ClearExpiresAt(t *testing.T) {
	db := openTestDB(t)

	exp := time.Date(2030, 6, 1, 0, 0, 0, 0, time.UTC)
	rec, _ := db.CreatePolicyRule("will clear", 100, `{"effect":"allow"}`, nil, nil, &exp)

	// Clear expires_at by passing non-nil outer, nil inner.
	var nilTime *time.Time
	if err := db.UpdatePolicyRule(rec.ID, nil, nil, nil, nil, &nilTime); err != nil {
		t.Fatalf("UpdatePolicyRule (clear expires_at): %v", err)
	}

	got, _ := db.GetPolicyRule(rec.ID)
	if got.ExpiresAt != nil {
		t.Errorf("expected ExpiresAt=nil after clear, got %v", *got.ExpiresAt)
	}
}
