package services

import (
	"context"
	"os"
	"testing"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"

	"mgit.msbls.de/m/paliad/internal/db"
)

// TestBroadcastService_SendAndAudit_Live exercises the full BroadcastService
// pipeline against a real Postgres: the row lands in paliad.email_broadcasts,
// the send_report jsonb captures per-recipient outcomes, and List/Get
// honours the visibility rules (sender sees own; global_admin sees all).
//
// SMTP delivery is not exercised — the MailService is left disabled
// (Enabled() == false) so sendOne short-circuits cleanly. That's the same
// contract the dev/preview deploys run under.
//
// Skipped when TEST_DATABASE_URL is unset.
func TestBroadcastService_SendAndAudit_Live(t *testing.T) {
	url := os.Getenv("TEST_DATABASE_URL")
	if url == "" {
		t.Skip("TEST_DATABASE_URL not set — skipping live DB test")
	}
	if err := db.ApplyMigrations(url); err != nil {
		t.Fatalf("apply migrations: %v", err)
	}
	pool, err := sqlx.Connect("postgres", url)
	if err != nil {
		t.Fatalf("connect: %v", err)
	}
	defer pool.Close()

	ctx := context.Background()
	leadID := uuid.New()
	memberID := uuid.New()
	otherSenderID := uuid.New()
	projectID := uuid.New()

	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.users (id, email, display_name, office, global_role, lang)
		 VALUES ($1, $2, 'Bcast Lead',  'munich', 'standard',     'de'),
		        ($3, $4, 'Bcast Mem',   'munich', 'standard',     'de'),
		        ($5, $6, 'Bcast Admin', 'munich', 'global_admin', 'de')`,
		leadID, "bcast-lead@hlc.com",
		memberID, "bcast-member@hlc.com",
		otherSenderID, "bcast-admin@hlc.com",
	); err != nil {
		t.Fatalf("seed users: %v", err)
	}
	t.Cleanup(func() {
		_, _ = pool.ExecContext(ctx, `DELETE FROM paliad.email_broadcasts WHERE sender_id = ANY($1)`,
			[]string{leadID.String(), otherSenderID.String()})
		_, _ = pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id = $1`, projectID)
		_, _ = pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, projectID)
		_, _ = pool.ExecContext(ctx, `DELETE FROM paliad.users WHERE id = ANY($1)`,
			[]string{leadID.String(), memberID.String(), otherSenderID.String()})
	})

	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.projects (id, type, path, title, status, created_by)
		 VALUES ($1, 'project', $1::text, 'Bcast Project', 'active', $2)`,
		projectID, leadID,
	); err != nil {
		t.Fatalf("seed project: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.project_teams (project_id, user_id, role, inherited, added_by)
		 VALUES ($1, $2, 'lead',      false, $2),
		        ($1, $3, 'associate', false, $2)`,
		projectID, leadID, memberID,
	); err != nil {
		t.Fatalf("seed team: %v", err)
	}

	users := NewUserService(pool)
	projectSvc := NewProjectService(pool, users)
	teamSvc := NewTeamService(pool, projectSvc)
	mailSvc, err := NewMailService()
	if err != nil {
		t.Fatalf("mail svc: %v", err)
	}
	tplSvc := NewEmailTemplateService(pool)
	mailSvc.SetTemplateService(tplSvc)
	bcast := NewBroadcastService(pool, mailSvc, users, teamSvc, tplSvc)

	// --- 1. lead can send a broadcast on their project --------------
	pid := projectID
	report, err := bcast.Send(ctx, leadID, BroadcastInput{
		ProjectID: &pid,
		Subject:   "Hallo Team",
		Body:      "Hi {{first_name}}, kurze Nachricht.",
		Recipients: []BroadcastRecipient{{
			UserID:        memberID,
			Email:         "bcast-member@hlc.com",
			DisplayName:   "Bcast Mem",
			FirstName:     "Bcast",
			RoleOnProject: "associate",
		}},
		RecipientFilter: map[string]any{"project_ids": []string{pid.String()}},
	})
	if err != nil {
		t.Fatalf("Send (lead): %v", err)
	}
	if report.BroadcastID == uuid.Nil {
		t.Fatal("BroadcastID empty")
	}
	if report.Total != 1 {
		t.Errorf("Total=%d, want 1", report.Total)
	}
	if report.Sent != 1 || report.Failed != 0 {
		t.Errorf("Sent=%d Failed=%d, want Sent=1 Failed=0", report.Sent, report.Failed)
	}

	// --- 2. non-lead sender (member) → forbidden --------------------
	_, err = bcast.Send(ctx, memberID, BroadcastInput{
		ProjectID: &pid,
		Subject:   "Should fail",
		Body:      "x",
		Recipients: []BroadcastRecipient{{
			UserID: leadID, Email: "bcast-lead@hlc.com", DisplayName: "Bcast Lead",
		}},
	})
	if err == nil || !errorIs(err, ErrBroadcastForbidden) {
		t.Errorf("non-lead Send: got %v, want ErrBroadcastForbidden", err)
	}

	// --- 3. global_admin sees all rows in List ----------------------
	rowsAdmin, err := bcast.List(ctx, otherSenderID, 50)
	if err != nil {
		t.Fatalf("List(admin): %v", err)
	}
	foundOurRow := false
	for _, r := range rowsAdmin {
		if r.ID == report.BroadcastID {
			foundOurRow = true
			if r.RecipientCount != 1 {
				t.Errorf("RecipientCount=%d, want 1", r.RecipientCount)
			}
		}
	}
	if !foundOurRow {
		t.Error("admin's List did not include our broadcast")
	}

	// --- 4. lead sees own rows --------------------------------------
	rowsLead, err := bcast.List(ctx, leadID, 50)
	if err != nil {
		t.Fatalf("List(lead): %v", err)
	}
	if len(rowsLead) == 0 || rowsLead[0].ID != report.BroadcastID {
		t.Errorf("lead List didn't return own row; got %+v", rowsLead)
	}

	// --- 5. non-sender, non-admin gets nothing back -----------------
	rowsMember, err := bcast.List(ctx, memberID, 50)
	if err != nil {
		t.Fatalf("List(member): %v", err)
	}
	for _, r := range rowsMember {
		if r.ID == report.BroadcastID {
			t.Errorf("member should not see lead's broadcast %s", r.ID)
		}
	}

	// --- 6. Get returns full detail w/ recipients -------------------
	detail, err := bcast.Get(ctx, leadID, report.BroadcastID)
	if err != nil {
		t.Fatalf("Get: %v", err)
	}
	if detail.Subject != "Hallo Team" {
		t.Errorf("Subject=%q", detail.Subject)
	}
	if len(detail.Recipients) != 1 {
		t.Errorf("Recipients=%d, want 1", len(detail.Recipients))
	}
	if len(detail.Recipients) >= 1 && detail.Recipients[0].UserID != memberID {
		t.Errorf("Recipients[0].UserID=%s, want %s", detail.Recipients[0].UserID, memberID)
	}

	// --- 7. member calling Get on lead's row → forbidden -----------
	if _, err := bcast.Get(ctx, memberID, report.BroadcastID); err == nil ||
		!errorIs(err, ErrBroadcastForbidden) {
		t.Errorf("member Get: got %v, want ErrBroadcastForbidden", err)
	}
}
