package services

import (
	"context"
	"errors"
	"os"
	"testing"
	"time"

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

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

// TestDeadlineReopen_AdminAndNonAdmin verifies the Reopen lifecycle end-to-end:
//   - admin can reopen a completed Deadline (status flips to pending,
//     completed_at cleared, project_events row appended)
//   - a visible-but-not-admin user gets ErrForbidden when calling Reopen
//
// Skipped when TEST_DATABASE_URL is unset.
func TestDeadlineReopen_AdminAndNonAdmin(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()

	adminID := uuid.New()
	memberID := uuid.New()
	projectID := uuid.New()
	deadlineID := uuid.New()

	cleanup := func() {
		pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id = $1`, projectID)
		pool.ExecContext(ctx, `DELETE FROM paliad.deadlines      WHERE id = $1`, deadlineID)
		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 IN ($1, $2)`, adminID, memberID)
		pool.ExecContext(ctx, `DELETE FROM auth.users            WHERE id IN ($1, $2)`, adminID, memberID)
	}
	cleanup()
	defer cleanup()

	if _, err := pool.ExecContext(ctx,
		`INSERT INTO auth.users (id, email) VALUES ($1, $2), ($3, $4)`,
		adminID, "reopen-admin@hlc.com", memberID, "reopen-member@hlc.com"); err != nil {
		t.Fatalf("seed auth.users: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.users (id, email, display_name, office, role, lang)
		 VALUES ($1, $2, 'Reopen Admin',  'munich', 'admin',     'de'),
		        ($3, $4, 'Reopen Member', 'munich', 'associate', 'de')`,
		adminID, "reopen-admin@hlc.com", memberID, "reopen-member@hlc.com"); err != nil {
		t.Fatalf("seed paliad.users: %v", err)
	}

	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.projects (id, type, path, title, reference, status, created_by)
		 VALUES ($1, 'project', $1::text, 'Reopen Test Project', '2026/9998', 'active', $2)`,
		projectID, adminID); err != nil {
		t.Fatalf("seed paliad.projects: %v", err)
	}

	// memberID is on the project team but only as 'associate', so Reopen must
	// reject them. Visibility should still let GetByID succeed.
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.project_teams (project_id, user_id, role, inherited, added_by)
		 VALUES ($1, $2, 'associate', false, $3)`,
		projectID, memberID, adminID); err != nil {
		t.Fatalf("seed paliad.project_teams: %v", err)
	}

	completedAt := time.Date(2026, 4, 20, 10, 0, 0, 0, time.UTC)
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.deadlines
		   (id, project_id, title, due_date, source, status, completed_at, created_by)
		 VALUES ($1, $2, 'Schriftsatz einreichen', $3, 'manual', 'completed', $4, $5)`,
		deadlineID, projectID, completedAt, completedAt, adminID); err != nil {
		t.Fatalf("seed paliad.deadlines: %v", err)
	}

	users := NewUserService(pool)
	projects := NewProjectService(pool, users)
	svc := NewDeadlineService(pool, projects, nil)

	// Non-admin associate cannot reopen.
	if _, err := svc.Reopen(ctx, memberID, deadlineID); !errors.Is(err, ErrForbidden) {
		t.Fatalf("Reopen as associate: err=%v, want ErrForbidden", err)
	}

	// Admin can reopen.
	got, err := svc.Reopen(ctx, adminID, deadlineID)
	if err != nil {
		t.Fatalf("Reopen as admin: %v", err)
	}
	if got.Status != "pending" {
		t.Errorf("Status after reopen = %q, want pending", got.Status)
	}
	if got.CompletedAt != nil {
		t.Errorf("CompletedAt after reopen = %v, want nil", got.CompletedAt)
	}

	// Audit row landed.
	var eventCount int
	if err := pool.GetContext(ctx, &eventCount,
		`SELECT COUNT(*) FROM paliad.project_events
		  WHERE project_id = $1 AND event_type = 'deadline_reopened'`,
		projectID); err != nil {
		t.Fatalf("count events: %v", err)
	}
	if eventCount != 1 {
		t.Errorf("deadline_reopened events = %d, want 1", eventCount)
	}

	// Reopen on an already-pending Deadline is a no-op (no extra event).
	again, err := svc.Reopen(ctx, adminID, deadlineID)
	if err != nil {
		t.Fatalf("Reopen idempotent: %v", err)
	}
	if again.Status != "pending" {
		t.Errorf("idempotent Reopen status = %q, want pending", again.Status)
	}
	if err := pool.GetContext(ctx, &eventCount,
		`SELECT COUNT(*) FROM paliad.project_events
		  WHERE project_id = $1 AND event_type = 'deadline_reopened'`,
		projectID); err != nil {
		t.Fatalf("count events post-noop: %v", err)
	}
	if eventCount != 1 {
		t.Errorf("idempotent reopen wrote %d audit rows, want 1", eventCount)
	}
}
