package main

import (
	"context"
	"log/slog"
	"os"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/envutil"
	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/internal/service"
	"github.com/orchard9/rdev/internal/worker"
)

// Config holds application configuration.
type Config struct {
	Port       int
	DBHost     string
	DBPort     int
	DBUser     string
	DBPassword string
	DBName     string
	DBSSLMode  string
	AdminKey   string

	// Credential store encryption key (required for storing secrets in DB)
	CredentialEncryptionKey string

	// OpenCode configuration (optional - enables OpenCode as alternative code agent)
	OpenCodeURL      string // e.g., "http://opencode:4096"
	OpenCodeUsername string // Basic auth username (default: "opencode")
	OpenCodePassword string // Basic auth password

	// Internal API token for service-to-service callbacks
	InternalToken string

	// Infrastructure adapters (threesix.ai) - fallback values if not in credential store
	GiteaURL                string
	GiteaToken              string
	GiteaDefaultOrg         string
	CloudflareToken         string
	CloudflareZoneID        string
	DefaultDomain           string
	DeployNamespace         string
	DeployTLSIssuer         string
	ClusterIP               string
	RegistryURL             string
	WoodpeckerURL           string
	WoodpeckerAPIToken      string
	WoodpeckerWebhookSecret string
}

// InfraConfig holds infrastructure adapter configuration.
// Loaded from credential store with env var fallback.
type InfraConfig struct {
	GiteaURL                string
	GiteaToken              string
	GiteaDefaultOrg         string
	CloudflareToken         string
	CloudflareZoneID        string
	DefaultDomain           string
	DeployNamespace         string
	DeployTLSIssuer         string
	ClusterIP               string
	RegistryURL             string
	WoodpeckerURL           string
	WoodpeckerAPIToken      string
	WoodpeckerWebhookSecret string

	// CockroachDB provisioner (for project databases)
	CRDBHost    string // e.g., "cockroachdb-public.databases.svc.cluster.local"
	CRDBPort    int    // e.g., 26257
	CRDBUser    string // e.g., "root" (insecure mode)
	CRDBSSLMode string // e.g., "disable" (insecure) or "verify-full" (production)

	// Redis provisioner (for project cache)
	RedisHost     string // e.g., "redis.databases.svc.cluster.local"
	RedisPort     int    // e.g., 6379
	RedisPassword string // admin password for ACL management

	// GCS provisioner (for project storage)
	GCSProjectID       string // e.g., "threesix-prod"
	GCSCredentialsPath string // Path to service account JSON (empty = ADC)
	GCSLocation        string // Bucket location (default: "US")
}

func loadConfig() Config {
	return Config{
		Port:       envutil.GetEnvInt("PORT", 8080),
		DBHost:     envutil.GetEnv("DB_HOST", "postgres.databases.svc.cluster.local"),
		DBPort:     envutil.GetEnvInt("DB_PORT", 5432),
		DBUser:     envutil.GetEnv("DB_USER", "appuser"),
		DBPassword: os.Getenv("DB_PASSWORD"),
		DBName:     envutil.GetEnv("DB_NAME", "rdev"),
		DBSSLMode:  envutil.GetEnv("DB_SSL_MODE", "disable"),
		AdminKey:   os.Getenv("RDEV_ADMIN_KEY"),

		// Encryption key for credential store (generate with: openssl rand -base64 32)
		// REQUIRED in production - no default to prevent insecure deployments
		CredentialEncryptionKey: os.Getenv("CREDENTIAL_ENCRYPTION_KEY"),

		// OpenCode (optional alternative code agent)
		OpenCodeURL:      os.Getenv("OPENCODE_URL"), // e.g., "http://opencode:4096"
		OpenCodeUsername: envutil.GetEnv("OPENCODE_USERNAME", "opencode"),
		OpenCodePassword: os.Getenv("OPENCODE_PASSWORD"),

		// Internal API token for service-to-service callbacks (e.g., SDLC callbacks)
		InternalToken: os.Getenv("INTERNAL_TOKEN"),

		// Infrastructure adapters (fallback if not in credential store)
		GiteaURL:                envutil.GetEnv("GITEA_URL", "https://git.threesix.ai"),
		GiteaToken:              os.Getenv("GITEA_TOKEN"),
		GiteaDefaultOrg:         envutil.GetEnv("GITEA_DEFAULT_ORG", "jordan"),
		CloudflareToken:         os.Getenv("CLOUDFLARE_API_TOKEN"),
		CloudflareZoneID:        os.Getenv("CLOUDFLARE_ZONE_ID"),
		DefaultDomain:           envutil.GetEnv("DEFAULT_DOMAIN", "threesix.ai"),
		DeployNamespace:         envutil.GetEnv("DEPLOY_NAMESPACE", "projects"),
		DeployTLSIssuer:         envutil.GetEnv("DEPLOY_TLS_ISSUER", "letsencrypt-prod"),
		ClusterIP:               envutil.GetEnv("CLUSTER_IP", "208.122.204.172"),
		RegistryURL:             envutil.GetEnv("REGISTRY_URL", "registry.threesix.ai"),
		WoodpeckerURL:           envutil.GetEnv("WOODPECKER_URL", "https://ci.threesix.ai"),
		WoodpeckerAPIToken:      os.Getenv("WOODPECKER_API_TOKEN"),
		WoodpeckerWebhookSecret: os.Getenv("WOODPECKER_WEBHOOK_SECRET"),
	}
}

// loadInfraConfig loads infrastructure configuration from credential store,
// falling back to environment variables if not found in the store.
func loadInfraConfig(ctx context.Context, store port.CredentialStore, cfg Config, logger *slog.Logger) InfraConfig {
	// Try to load from credential store
	creds, err := store.GetMultiple(ctx, []string{
		domain.CredKeyGiteaToken,
		domain.CredKeyGiteaURL,
		domain.CredKeyCloudflareAPIToken,
		domain.CredKeyCloudflareZoneID,
		domain.CredKeyWoodpeckerURL,
		domain.CredKeyWoodpeckerAPIToken,
		domain.CredKeyWoodpeckerWebhookSecret,
		domain.CredKeyRegistryURL,
	})
	if err != nil {
		logger.Warn("failed to load credentials from store, using env vars", "error", err)
		creds = make(map[string]string)
	}

	// Helper to get from store or fall back to env var
	getOrFallback := func(key, envFallback string) string {
		if v, ok := creds[key]; ok && v != "" {
			return v
		}
		return envFallback
	}

	infraCfg := InfraConfig{
		GiteaURL:                getOrFallback(domain.CredKeyGiteaURL, cfg.GiteaURL),
		GiteaToken:              getOrFallback(domain.CredKeyGiteaToken, cfg.GiteaToken),
		GiteaDefaultOrg:         cfg.GiteaDefaultOrg, // Not a secret, use env
		CloudflareToken:         getOrFallback(domain.CredKeyCloudflareAPIToken, cfg.CloudflareToken),
		CloudflareZoneID:        getOrFallback(domain.CredKeyCloudflareZoneID, cfg.CloudflareZoneID),
		DefaultDomain:           cfg.DefaultDomain,   // Not a secret, use env
		DeployNamespace:         cfg.DeployNamespace, // Not a secret, use env
		DeployTLSIssuer:         cfg.DeployTLSIssuer, // Not a secret, use env
		ClusterIP:               cfg.ClusterIP,       // Not a secret, use env
		RegistryURL:             getOrFallback(domain.CredKeyRegistryURL, cfg.RegistryURL),
		WoodpeckerURL:           getOrFallback(domain.CredKeyWoodpeckerURL, cfg.WoodpeckerURL),
		WoodpeckerAPIToken:      getOrFallback(domain.CredKeyWoodpeckerAPIToken, cfg.WoodpeckerAPIToken),
		WoodpeckerWebhookSecret: getOrFallback(domain.CredKeyWoodpeckerWebhookSecret, cfg.WoodpeckerWebhookSecret),

		// CockroachDB and Redis provisioners (env-only for now)
		CRDBHost:      os.Getenv("CRDB_HOST"), // e.g., "cockroachdb-public.databases.svc.cluster.local"
		CRDBPort:      envutil.GetEnvInt("CRDB_PORT", 26257),
		CRDBUser:      envutil.GetEnv("CRDB_USER", "root"),
		CRDBSSLMode:   envutil.GetEnv("CRDB_SSL_MODE", "disable"),
		RedisHost:     os.Getenv("REDIS_HOST"), // e.g., "redis.databases.svc.cluster.local"
		RedisPort:     envutil.GetEnvInt("REDIS_PORT", 6379),
		RedisPassword: os.Getenv("REDIS_PASSWORD"),

		// GCS provisioner (env-only)
		GCSProjectID:       os.Getenv("GCS_PROJECT_ID"),
		GCSCredentialsPath: os.Getenv("GCS_CREDENTIALS_PATH"),
		GCSLocation:        envutil.GetEnv("GCS_LOCATION", "US"),
	}

	// Log which credentials were loaded from store vs env
	fromStore := 0
	for k := range creds {
		if creds[k] != "" {
			fromStore++
		}
	}
	if fromStore > 0 {
		logger.Info("loaded credentials from store", "count", fromStore)
	}

	return infraCfg
}

// closeProvisioner attempts to close a provisioner that implements io.Closer.
func closeProvisioner(p any, name string, logger *slog.Logger) {
	if p == nil {
		return
	}
	if closer, ok := p.(interface{ Close() error }); ok {
		if err := closer.Close(); err != nil {
			logger.Warn("failed to close "+name+" provisioner", "error", err)
		}
	}
}

// podGitCommitterAdapter wraps worker.PodGitOperations to satisfy
// service.PodGitCommitter without creating an import cycle.
type podGitCommitterAdapter struct {
	podGitOps *worker.PodGitOperations
}

func (a *podGitCommitterAdapter) CommitAndPush(ctx context.Context, podName, workDir, message string, push bool) *service.GitCommitResult {
	result := a.podGitOps.CommitAndPush(ctx, podName, workDir, message, push)
	return &service.GitCommitResult{
		HasChanges:   result.HasChanges,
		CommitSHA:    result.CommitSHA,
		FilesChanged: result.FilesChanged,
		Pushed:       result.Pushed,
		Error:        result.Error,
	}
}
