package store

import (
	"context"
	"errors"
	"testing"
	"time"
)

// TestDeleteHostCascades verifies that DeleteHost removes the host
// row and that every dependent table (schedules, jobs, source groups,
// host_credentials) is wiped via the FK cascade declared in the
// migrations. We also verify the agent bearer is no longer resolvable
// — a re-installed agent must come back through pending-host accept.
func TestDeleteHostCascades(t *testing.T) {
	t.Parallel()
	s := openTestStore(t)
	ctx := context.Background()

	hostID := makeSchedHost(t, s)
	gid := makeGroup(t, s, hostID, "default", "01HDELGRP000000000000001")

	// One job, one schedule, one credential row — enough to prove the
	// cascade reaches every dependent table we care about.
	if err := s.CreateJob(ctx, Job{
		ID: "j-del-1", HostID: hostID, Kind: "backup",
		ActorKind: "system", CreatedAt: time.Now().UTC(),
	}); err != nil {
		t.Fatalf("create job: %v", err)
	}

	sched := &Schedule{
		ID:             "01HDELSCHED00000000000001",
		HostID:         hostID,
		CronExpr:       "0 3 * * *",
		Enabled:        true,
		SourceGroupIDs: []string{gid},
	}
	if err := s.CreateSchedule(ctx, sched); err != nil {
		t.Fatalf("create schedule: %v", err)
	}

	if err := s.SetHostCredentials(ctx, hostID, CredKindRepo, "ciphertext"); err != nil {
		t.Fatalf("set creds: %v", err)
	}

	// Sanity: agent bearer resolves before deletion.
	if _, err := s.LookupHostByAgentToken(ctx, "tokenhash"); err != nil {
		t.Fatalf("pre-delete bearer lookup: %v", err)
	}

	if err := s.DeleteHost(ctx, hostID); err != nil {
		t.Fatalf("DeleteHost: %v", err)
	}

	if _, err := s.GetHost(ctx, hostID); !errors.Is(err, ErrNotFound) {
		t.Errorf("GetHost after delete: want ErrNotFound, got %v", err)
	}
	if _, err := s.LookupHostByAgentToken(ctx, "tokenhash"); !errors.Is(err, ErrNotFound) {
		t.Errorf("bearer lookup after delete: want ErrNotFound, got %v", err)
	}

	// Cascade smoke-tests via raw counts. We don't own a public
	// "list jobs by host" path that filters by host, so go to the DB
	// directly with the same connection used by the store helpers.
	for _, q := range []struct {
		label string
		sql   string
	}{
		{"schedules", "SELECT count(*) FROM schedules WHERE host_id = ?"},
		{"jobs", "SELECT count(*) FROM jobs WHERE host_id = ?"},
		{"source_groups", "SELECT count(*) FROM source_groups WHERE host_id = ?"},
		{"host_credentials", "SELECT count(*) FROM host_credentials WHERE host_id = ?"},
		{"schedule_source_groups", "SELECT count(*) FROM schedule_source_groups WHERE schedule_id = ?"},
	} {
		var n int
		key := hostID
		if q.label == "schedule_source_groups" {
			key = "01HDELSCHED00000000000001"
		}
		if err := s.db.QueryRowContext(ctx, q.sql, key).Scan(&n); err != nil {
			t.Fatalf("count %s: %v", q.label, err)
		}
		if n != 0 {
			t.Errorf("cascade left %d rows in %s", n, q.label)
		}
	}
}

// TestDeleteHostNotFound: a delete against a missing id surfaces
// ErrNotFound so the HTTP layer can 404 instead of 200-ing a no-op.
func TestDeleteHostNotFound(t *testing.T) {
	t.Parallel()
	s := openTestStore(t)
	if err := s.DeleteHost(context.Background(), "01HNOTAHOST00000000000000"); !errors.Is(err, ErrNotFound) {
		t.Errorf("missing id: want ErrNotFound, got %v", err)
	}
}
