//go:build integration

package integrationtest

import (
	"context"
	"fmt"
	"net/http/httptest"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/redis/go-redis/v9"

	"github.com/ultisuite/ulti-backend/internal/auth"
	"github.com/ultisuite/ulti-backend/internal/config"
	"github.com/ultisuite/ulti-backend/internal/dbmigrate"
	"github.com/ultisuite/ulti-backend/internal/server"
)

// Harness is the shared integration test environment.
type Harness struct {
	Env    Env
	Infra  *infra
	OIDC   *OIDCServer
	App    *server.App
	Server *httptest.Server
	Pool   *pgxpool.Pool
	Redis  *redis.Client
}

var (
	harnessMu       sync.Mutex
	harnessInst     *Harness
	harnessRefCount int
	harnessErr      error
)

// Get returns the shared harness, initializing containers on first call.
func Get() (*Harness, error) {
	harnessMu.Lock()
	defer harnessMu.Unlock()

	if harnessInst != nil {
		return harnessInst, nil
	}
	if harnessErr != nil {
		return nil, harnessErr
	}

	harnessInst, harnessErr = newHarness(context.Background())
	return harnessInst, harnessErr
}

func releaseHarness(ctx context.Context) {
	harnessMu.Lock()
	defer harnessMu.Unlock()

	harnessRefCount--
	if harnessRefCount > 0 || harnessInst == nil {
		return
	}
	harnessInst.Close(ctx)
	harnessInst = nil
	harnessErr = nil
}

// RunMain is called from TestMain in integration test packages.
func RunMain(m *testing.M) int {
	env := LoadEnv()
	if !env.Enabled {
		fmt.Fprintln(os.Stderr, "integration tests skipped: set ULTI_TEST_INTEGRATION=1")
		return 0
	}

	harnessMu.Lock()
	harnessRefCount++
	harnessMu.Unlock()

	h, err := Get()
	if err != nil {
		fmt.Fprintf(os.Stderr, "integration harness setup failed: %v\n", err)
		return 1
	}
	_ = h

	code := m.Run()
	releaseHarness(context.Background())
	return code
}

func newHarness(ctx context.Context) (*Harness, error) {
	env := LoadEnv()
	if !env.Enabled {
		return nil, fmt.Errorf("ULTI_TEST_INTEGRATION is not set")
	}

	infra, err := sharedInfra(ctx, env)
	if err != nil {
		return nil, err
	}

	if env.AutoMigrate {
		if err := dbmigrate.Up(infra.dbURL); err != nil {
			return nil, fmt.Errorf("migrate: %w", err)
		}
	}

	oidc, err := NewOIDCServer()
	if err != nil {
		return nil, err
	}

	holder, err := oidc.Holder(ctx)
	if err != nil {
		return nil, fmt.Errorf("oidc holder: %w", err)
	}

	pool, err := pgxpool.New(ctx, infra.dbURL)
	if err != nil {
		return nil, fmt.Errorf("pg pool: %w", err)
	}

	rdb := redis.NewClient(&redis.Options{Addr: infra.redisAddr})

	cfg := buildTestConfig(env, infra, oidc)

	app, err := server.New(ctx, cfg, server.Options{
		WithoutWorkers:           true,
		SkipAuthentikProvisioner: true,
		VerifierHolder:           holder,
		Pool:                     pool,
		Redis:                    rdb,
	})
	if err != nil {
		pool.Close()
		_ = rdb.Close()
		return nil, fmt.Errorf("server bootstrap: %w", err)
	}

	ts := httptest.NewServer(app.Router)

	return &Harness{
		Env:    env,
		Infra:  infra,
		OIDC:   oidc,
		App:    app,
		Server: ts,
		Pool:   pool,
		Redis:  rdb,
	}, nil
}

func buildTestConfig(env Env, infra *infra, oidc *OIDCServer) *config.Config {
	cfg := &config.Config{
		Port:                      0,
		Domain:                    "localhost",
		AppEnv:                    "development",
		DatabaseURL:               infra.dbURL,
		KeyDBAddr:                 infra.redisAddr,
		KeyDBDB:                   0,
		RustFSEndpoint:            normalizeEndpoint(infra.s3Endpoint),
		RustFSAccessKey:           infra.s3AccessKey,
		RustFSSecretKey:           infra.s3SecretKey,
		RustFSUseSSL:              env.S3UseSSL,
		RustFSRegion:              "us-east-1",
		OIDCIssuer:                oidc.Issuer,
		OIDCClientID:              oidc.ClientID,
		NextcloudEnabled:          env.Nextcloud && env.NextcloudURL != "",
		NextcloudURL:              env.NextcloudURL,
		NextcloudPublicURL:        env.NextcloudURL,
		NCAdminUser:               "admin",
		NCAdminPass:               "test",
		OnlyOfficeEnabled:         false,
		JitsiEnabled:              env.Jitsi,
		JitsiDomain:               "meet.localhost",
		JitsiAppID:                "ulti",
		JitsiAppSecret:            "test-jitsi-secret",
		ImmichEnabled:             env.Immich && env.ImmichURL != "",
		ImmichAPIURL:              env.ImmichURL,
		MailAttachmentsBucket:     "mail-attachments-test",
		MailSyncInterval:          time.Minute,
		MailOutboxInterval:        time.Minute,
		MailOutboxMaxRetries:      3,
		MailSendRatePerMinute:     100,
		MailSendBurst:             50,
		MailSMTPCircuitFailures:   5,
		MailSMTPCircuitCooldown:   time.Minute,
		MailCredentialKeys:        "v1:MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
		MailActiveCredentialKeyID: "v1",
		MailWebhookSharedSecret:   "test-webhook-secret",
		MailAppURL:                "http://localhost:3000",
		SearchEngine:              "postgres",
		MeilisearchURL:            env.MeilisearchURL,
		MeilisearchKey:            env.MeilisearchKey,
		HealthHTTPTimeout:         2 * time.Second,
	}
	if env.Meilisearch && env.MeilisearchURL != "" {
		cfg.SearchEngine = "meilisearch"
	}
	return cfg
}

// Close releases test resources.
func (h *Harness) Close(ctx context.Context) {
	if h == nil {
		return
	}
	if h.Server != nil {
		h.Server.Close()
	}
	if h.App != nil {
		h.App.Close()
	} else {
		if h.Pool != nil {
			h.Pool.Close()
		}
		if h.Redis != nil {
			_ = h.Redis.Close()
		}
	}
	if h.OIDC != nil {
		h.OIDC.Close()
	}
	if h.Infra != nil {
		h.Infra.Close(ctx)
	}
}

// NewExternalID returns a unique OIDC subject for test isolation.
func NewExternalID(prefix string) string {
	if prefix == "" {
		prefix = "test"
	}
	return fmt.Sprintf("%s-%s", prefix, uuid.NewString())
}

// IssueToken signs an OIDC ID token for the given claims.
func (h *Harness) IssueToken(claims *auth.Claims) (string, error) {
	return h.OIDC.IssueToken(claims)
}

// Client returns an authenticated HTTP client for the given claims.
func (h *Harness) Client(claims *auth.Claims) (*Client, error) {
	token, err := h.IssueToken(claims)
	if err != nil {
		return nil, err
	}
	return NewClient(h.Server.URL, token), nil
}

// RequireHarness is a test helper that fails the test if harness setup fails.
func RequireHarness(t *testing.T) *Harness {
	t.Helper()
	if !LoadEnv().Enabled {
		t.Skip("integration tests disabled: set ULTI_TEST_INTEGRATION=1")
	}
	h, err := Get()
	if err != nil {
		t.Fatalf("harness: %v", err)
	}
	return h
}
