package config

import (
	"errors"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ultisuite/ulti-backend/internal/secrets"
)

type Config struct {
	Port   int
	Domain string
	AppEnv string

	// Browser clients (web app on another origin than the API gateway).
	CORSAllowedOrigins []string

	// PostgreSQL
	DatabaseURL string

	// KeyDB
	KeyDBAddr     string
	KeyDBPassword string
	KeyDBDB       int

	// Object Storage (S3-compatible)
	RustFSEndpoint  string
	RustFSAccessKey string
	RustFSSecretKey string
	RustFSUseSSL    bool
	RustFSRegion    string

	// OIDC
	OIDCIssuer       string
	OIDCClientID     string
	OIDCClientSecret string

	// Authentik API (suite app auto-provisioning)
	AuthentikAPIURL      string
	AuthentikAPIToken    string
	AuthentikPublicHTTPS bool

	// Suite OIDC clients (Authentik applications)
	NCOIDCClientID             string
	NCOIDCClientSecret         string
	OnlyOfficeOIDCClientID     string
	OnlyOfficeOIDCClientSecret string
	ImmichOIDCClientID         string
	ImmichOIDCClientSecret     string
	DriveOIDCClientID          string
	DriveOIDCClientSecret      string

	// Nextcloud
	NextcloudEnabled   bool
	NextcloudURL       string
	NextcloudPublicURL string
	NCAdminUser        string
	NCAdminPass        string

	// OnlyOffice
	OnlyOfficeEnabled        bool
	OnlyOfficeURL            string
	OnlyOfficePublicURL      string
	OnlyOfficeAPIInternalURL string
	OnlyOfficeJWTSecret      string
	UltidPublicURL      string
	DrivePublicURL      string

	// Jitsi
	JitsiEnabled   bool
	JitsiDomain    string
	JitsiAppID     string
	JitsiAppSecret string
	JitsiPublicURL string

	// Immich
	ImmichEnabled bool
	ImmichAPIURL  string

	// Mail
	MailAttachmentsBucket     string
	MailSyncInterval          time.Duration
	MailOutboxInterval        time.Duration
	MailOutboxMaxRetries      int
	MailSendRatePerMinute     int
	MailSendBurst             int
	MailSMTPCircuitFailures   int
	MailSMTPCircuitCooldown   time.Duration
	MailCredentialKeys        string
	MailActiveCredentialKeyID string
	MailWebhookSharedSecret   string

	MailGoogleOAuthClientID     string
	MailGoogleOAuthClientSecret string
	MailMicrosoftOAuthClientID  string
	MailMicrosoftOAuthSecret    string
	MailMicrosoftOAuthTenant    string
	MailOAuthRedirectURL        string
	MailAppURL                  string

	// Secret rotation policy
	SecretRotationMaxAge         time.Duration
	OIDCSecretRotatedAt          time.Time
	SMTPCredentialKeyRotatedAt   time.Time
	WebhookSharedSecretRotatedAt time.Time

	// Search
	SearchEngine        string
	MeilisearchURL      string
	MeilisearchKey      string
	MeilisearchIndex    string
	TypesenseURL        string
	TypesenseKey        string
	TypesenseCollection string

	// VirusTotal (optional env fallback for org file_policies.virustotal_api_key)
	VirusTotalAPIKey string

	// Observability
	HealthNextcloudURL string
	HealthImmichURL    string
	HealthJitsiURL     string
	HealthHTTPTimeout  time.Duration
}

func Load() (*Config, error) {
	port := 8080
	if v := os.Getenv("ULTID_PORT"); v != "" {
		if p, err := strconv.Atoi(v); err == nil {
			port = p
		}
	}

	return &Config{
		Port:   port,
		Domain: envOrDefault("DOMAIN", "localhost"),
		AppEnv: strings.ToLower(envOrDefault("ULTID_ENV", envOrDefault("APP_ENV", "development"))),
		CORSAllowedOrigins: parseCSVEnv("ULTID_CORS_ALLOWED_ORIGINS"),

		DatabaseURL:   envOrDefault("ULTID_DB_URL", "postgres://ulti:changeme@localhost:5432/ultidb?sslmode=disable"),
		KeyDBAddr:     envOrDefault("ULTID_KEYDB_URL", "localhost:6379"),
		KeyDBPassword: secrets.Env("ULTID_KEYDB_PASSWORD"),
		KeyDBDB:       envInt("ULTID_KEYDB_DB", 0),

		RustFSEndpoint:  envOrDefault("ULTID_RUSTFS_ENDPOINT", "localhost:9000"),
		RustFSAccessKey: secrets.Env("ULTID_RUSTFS_ACCESS_KEY"),
		RustFSSecretKey: secrets.Env("ULTID_RUSTFS_SECRET_KEY"),
		RustFSUseSSL:    envBool("ULTID_RUSTFS_USE_SSL", false),
		RustFSRegion:    envOrDefault("ULTID_RUSTFS_REGION", "us-east-1"),

		OIDCIssuer:       os.Getenv("ULTID_OIDC_ISSUER"),
		OIDCClientID:     os.Getenv("ULTID_OIDC_CLIENT_ID"),
		OIDCClientSecret: secrets.Env("ULTID_OIDC_CLIENT_SECRET"),

		AuthentikAPIURL:      envOrDefault("AUTHENTIK_API_URL", "http://authentik-server:9000"),
		AuthentikAPIToken:    secrets.Env("AUTHENTIK_API_TOKEN"),
		AuthentikPublicHTTPS: authentikPublicHTTPS(),

		NCOIDCClientID:             envOrDefault("NC_OIDC_CLIENT_ID", "ulti-nextcloud"),
		NCOIDCClientSecret:         secrets.Env("NC_OIDC_CLIENT_SECRET"),
		OnlyOfficeOIDCClientID:     envOrDefault("ONLYOFFICE_OIDC_CLIENT_ID", "ulti-onlyoffice"),
		OnlyOfficeOIDCClientSecret: secrets.Env("ONLYOFFICE_OIDC_CLIENT_SECRET"),
		ImmichOIDCClientID:         envOrDefault("IMMICH_OIDC_CLIENT_ID", "ulti-immich"),
		ImmichOIDCClientSecret:     secrets.Env("IMMICH_OIDC_CLIENT_SECRET"),
		DriveOIDCClientID:          os.Getenv("DRIVE_OIDC_CLIENT_ID"),
		DriveOIDCClientSecret:      secrets.Env("DRIVE_OIDC_CLIENT_SECRET"),

		NextcloudEnabled:   envBool("NEXTCLOUD_ENABLED", true),
		NextcloudURL:       envOrDefault("NEXTCLOUD_URL", "http://nextcloud:80"),
		NextcloudPublicURL: nextcloudPublicURL(),
		NCAdminUser:        envOrDefault("NC_ADMIN_USER", "admin"),
		NCAdminPass:        envOrDefaultSecret("NC_ADMIN_PASSWORD", "changeme"),

		OnlyOfficeEnabled:        envBool("ONLYOFFICE_ENABLED", false),
		OnlyOfficeURL:            envOrDefault("ONLYOFFICE_URL", "http://onlyoffice"),
		OnlyOfficePublicURL:      envOrDefault("ONLYOFFICE_PUBLIC_URL", "http://localhost/office"),
		OnlyOfficeAPIInternalURL: envOrDefault("ONLYOFFICE_API_INTERNAL_URL", "http://ultid:8080"),
		OnlyOfficeJWTSecret:      secrets.Env("ONLYOFFICE_JWT_SECRET"),
		UltidPublicURL:      envOrDefault("ULTID_PUBLIC_URL", "http://localhost"),
		DrivePublicURL:      drivePublicURL(),

		JitsiEnabled:   envBool("JITSI_ENABLED", true),
		JitsiDomain:    envOrDefault("JITSI_DOMAIN", "meet.jitsi"),
		JitsiAppID:     envOrDefault("JITSI_APP_ID", "ulti"),
		JitsiAppSecret: envOrDefaultSecret("JITSI_APP_SECRET", "changeme-jwt-secret"),
		JitsiPublicURL: envOrDefault("JITSI_PUBLIC_URL", "https://localhost/meet"),

		ImmichEnabled: envBool("IMMICH_ENABLED", true),
		ImmichAPIURL:  envOrDefault("IMMICH_API_URL", "http://immich-server:2283/api"),

		MailAttachmentsBucket:     envOrDefault("MAIL_ATTACHMENTS_BUCKET", "mail-attachments"),
		MailSyncInterval:          envDuration("MAIL_SYNC_INTERVAL", 2*time.Minute),
		MailOutboxInterval:        envDuration("MAIL_OUTBOX_INTERVAL", 10*time.Second),
		MailOutboxMaxRetries:      envInt("MAIL_OUTBOX_MAX_RETRIES", 8),
		MailSendRatePerMinute:     envInt("MAIL_SEND_RATE_PER_MINUTE", 30),
		MailSendBurst:             envInt("MAIL_SEND_BURST", 10),
		MailSMTPCircuitFailures:   envInt("MAIL_SMTP_CIRCUIT_FAILURES", 5),
		MailSMTPCircuitCooldown:   envDuration("MAIL_SMTP_CIRCUIT_COOLDOWN", 5*time.Minute),
		MailCredentialKeys:        secrets.Env("MAIL_CREDENTIAL_KEYS"),
		MailActiveCredentialKeyID: envOrDefault("MAIL_ACTIVE_CREDENTIAL_KEY_ID", ""),
		MailWebhookSharedSecret:   secrets.Env("MAIL_WEBHOOK_SHARED_SECRET"),

		MailGoogleOAuthClientID:     os.Getenv("MAIL_GOOGLE_OAUTH_CLIENT_ID"),
		MailGoogleOAuthClientSecret: secrets.Env("MAIL_GOOGLE_OAUTH_CLIENT_SECRET"),
		MailMicrosoftOAuthClientID:  os.Getenv("MAIL_MICROSOFT_OAUTH_CLIENT_ID"),
		MailMicrosoftOAuthSecret:    secrets.Env("MAIL_MICROSOFT_OAUTH_CLIENT_SECRET"),
		MailMicrosoftOAuthTenant:    envOrDefault("MAIL_MICROSOFT_OAUTH_TENANT", "common"),
		MailOAuthRedirectURL:        os.Getenv("MAIL_OAUTH_REDIRECT_URL"),
		MailAppURL:                  envOrDefault("MAIL_APP_URL", envOrDefault("NEXT_PUBLIC_APP_URL", "http://localhost:3000")),

		SecretRotationMaxAge:         envDuration("SECRET_ROTATION_MAX_AGE", 90*24*time.Hour),
		OIDCSecretRotatedAt:          envTime("ULTID_OIDC_CLIENT_SECRET_ROTATED_AT"),
		SMTPCredentialKeyRotatedAt:   envTime("MAIL_CREDENTIAL_KEY_ROTATED_AT"),
		WebhookSharedSecretRotatedAt: envTime("MAIL_WEBHOOK_SHARED_SECRET_ROTATED_AT"),

		SearchEngine:        envOrDefault("SEARCH_ENGINE", "postgres"),
		MeilisearchURL:      os.Getenv("MEILISEARCH_URL"),
		MeilisearchKey:      secrets.Env("MEILISEARCH_API_KEY"),
		MeilisearchIndex:    envOrDefault("MEILISEARCH_INDEX", "ulti"),
		TypesenseURL:        os.Getenv("TYPESENSE_URL"),
		TypesenseKey:        secrets.Env("TYPESENSE_API_KEY"),
		TypesenseCollection: envOrDefault("TYPESENSE_COLLECTION", "ulti"),

		VirusTotalAPIKey: secrets.Env("VIRUSTOTAL_API_KEY"),

		HealthNextcloudURL: envOrDefault("HEALTH_NEXTCLOUD_URL", joinURL(envOrDefault("NEXTCLOUD_URL", "http://nextcloud:80"), "/status.php")),
		HealthImmichURL:    envOrDefault("HEALTH_IMMICH_URL", joinURL(envOrDefault("IMMICH_API_URL", "http://immich-server:2283/api"), "/server-info/ping")),
		HealthJitsiURL:     envOrDefault("HEALTH_JITSI_URL", defaultHealthJitsiURL(envOrDefault("JITSI_PUBLIC_URL", "https://localhost/meet"))),
		HealthHTTPTimeout:  envDuration("HEALTH_HTTP_TIMEOUT", 3*time.Second),
	}, nil
}

func (c *Config) IsProduction() bool {
	return c != nil && c.AppEnv == "production"
}

func (c *Config) ValidateSecretRotation() error {
	if c == nil {
		return nil
	}

	maxAge := c.SecretRotationMaxAge
	if maxAge <= 0 {
		maxAge = 90 * 24 * time.Hour
	}

	now := time.Now()
	checks := []struct {
		name      string
		rotatedAt time.Time
	}{
		{name: "ULTID_OIDC_CLIENT_SECRET", rotatedAt: c.OIDCSecretRotatedAt},
		{name: "MAIL_CREDENTIAL_KEY", rotatedAt: c.SMTPCredentialKeyRotatedAt},
		{name: "MAIL_WEBHOOK_SHARED_SECRET", rotatedAt: c.WebhookSharedSecretRotatedAt},
	}

	var stale []string
	for _, check := range checks {
		if check.rotatedAt.IsZero() || now.Sub(check.rotatedAt) > maxAge {
			stale = append(stale, check.name)
		}
	}

	if len(stale) == 0 {
		return nil
	}
	return errors.New("stale or missing rotation metadata for: " + strings.Join(stale, ", "))
}

func envOrDefault(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

func parseCSVEnv(key string) []string {
	raw := strings.TrimSpace(os.Getenv(key))
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part != "" {
			out = append(out, part)
		}
	}
	return out
}

func envOrDefaultSecret(key, fallback string) string {
	if v := secrets.Env(key); v != "" {
		return v
	}
	return fallback
}

func envBool(key string, fallback bool) bool {
	v := os.Getenv(key)
	if v == "" {
		return fallback
	}
	b, err := strconv.ParseBool(v)
	if err != nil {
		return fallback
	}
	return b
}

func envInt(key string, fallback int) int {
	v := os.Getenv(key)
	if v == "" {
		return fallback
	}
	n, err := strconv.Atoi(v)
	if err != nil {
		return fallback
	}
	return n
}

func envDuration(key string, fallback time.Duration) time.Duration {
	v := os.Getenv(key)
	if v == "" {
		return fallback
	}
	d, err := time.ParseDuration(v)
	if err != nil {
		return fallback
	}
	return d
}

func envTime(key string) time.Time {
	v := os.Getenv(key)
	if v == "" {
		return time.Time{}
	}
	ts, err := time.Parse(time.RFC3339, v)
	if err != nil {
		return time.Time{}
	}
	return ts
}

func joinURL(base, path string) string {
	return strings.TrimRight(base, "/") + path
}

// nextcloudPublicURL is the external WebDAV base (OVERWRITECLIURL), used for MOVE/COPY Destination headers.
func nextcloudPublicURL() string {
	if v := strings.TrimSpace(os.Getenv("NC_PUBLIC_URL")); v != "" {
		return strings.TrimRight(v, "/")
	}
	domain := envOrDefault("DOMAIN", "localhost")
	proto := envOrDefault("NC_OVERWRITE_PROTOCOL", "http")
	return proto + "://" + domain + "/cloud"
}

// drivePublicURL is the browser-facing UltiDrive base used in public share links.
func drivePublicURL() string {
	if v := strings.TrimSpace(os.Getenv("DRIVE_PUBLIC_URL")); v != "" {
		return strings.TrimRight(v, "/")
	}
	return strings.TrimRight(envOrDefault("ULTID_PUBLIC_URL", "http://localhost"), "/") + "/drive"
}

func defaultHealthJitsiURL(publicURL string) string {
	trimmed := strings.TrimRight(publicURL, "/")
	trimmed = strings.TrimSuffix(trimmed, "/meet")
	return trimmed + "/about/health"
}

func authentikPublicHTTPS() bool {
	if envBool("AUTHENTIK_PUBLIC_HTTPS", false) {
		return true
	}
	host := strings.TrimSpace(os.Getenv("AUTHENTIK_HOST"))
	return strings.HasPrefix(strings.ToLower(host), "https://")
}
