package admin

import (
	"context"
	"encoding/json"
	"strings"
	"time"

	"github.com/jackc/pgx/v5"

	"github.com/ultisuite/ulti-backend/internal/config"
	"github.com/ultisuite/ulti-backend/internal/authentik"
)

const orgSettingsSingletonID = 1

func defaultOrgPolicy() map[string]any {
	return map[string]any{
		"authentik": map[string]any{
			"enabled":                  true,
			"api_url":                  "",
			"slug":                     "ulti-suite",
			"client_id":                "",
			"enforce_sso":              true,
			"allow_password_fallback":  false,
			"default_groups":           "ulti-users",
		},
		"identity_providers": map[string]any{
			"allow_self_enrollment": true,
			"default_login_source":  "",
			"providers":             []any{},
		},
		"two_factor": map[string]any{
			"required_for_all":     false,
			"required_for_admins":  true,
			"allowed_methods":      []any{"totp", "webauthn"},
			"grace_period_days":    7,
			"remember_device_days": 30,
		},
		"storage_quotas": map[string]any{
			"default_mail_gib":     5,
			"default_drive_gib":    5,
			"default_photos_gib":   5,
			"warn_threshold_pct":   90,
		},
		"usage_quotas": map[string]any{
			"llm_requests_per_day":      100,
			"llm_tokens_per_month":      500000,
			"search_requests_per_day":   50,
			"max_api_tokens_per_user":   10,
			"max_webhooks_per_user":     20,
		},
		"file_policies": map[string]any{
			"max_upload_mib":           512,
			"allowed_extensions":       "",
			"block_executable":         true,
			"external_sharing":         "authenticated",
			"default_link_expiry_days": 30,
			"virus_scan_enabled":       false,
			"virustotal_api_key":       "",
			"retention_trash_days":     30,
		},
		"llm": map[string]any{
			"default_provider_id":     "",
			"providers":               []any{},
			"enforce_org_providers":   false,
			"allow_user_override":     true,
		},
		"search": map[string]any{
			"suite_engine":         "postgres",
			"meilisearch_url":      "",
			"meilisearch_api_key":  "",
			"typesense_url":        "",
			"typesense_api_key":    "",
			"web_search": map[string]any{
				"default_provider_id": "brave-default",
				"providers":           []any{},
			},
			"enforce_org_search": false,
		},
		"administrators": []any{},
		"nextcloud": map[string]any{
			"enabled":           false,
			"base_url":          "",
			"admin_user":        "",
			"admin_password":    "",
			"drive_enabled":     true,
			"calendar_enabled":  true,
			"contacts_enabled":  true,
			"talk_enabled":      false,
		},
		"mailing": map[string]any{
			"enabled":       false,
			"smtp_host":     "",
			"smtp_port":     587,
			"smtp_user":     "",
			"smtp_password": "",
			"from_email":    "noreply@example.com",
			"from_name":     "Ulti Suite",
			"tls_mode":      "starttls",
		},
		"onlyoffice": map[string]any{
			"enabled":               false,
			"document_server_url":   "",
			"jwt_secret":            "",
			"jwt_header":            "Authorization",
		},
		"plugins": []any{
			map[string]any{"id": "mail-automation", "name": "Automatisations mail", "description": "Règles, webhooks et tri IA sur la réception.", "enabled": true, "version": "1.0.0"},
			map[string]any{"id": "contact-discovery", "name": "Découverte contacts", "description": "Enrichissement IA et signatures détectées.", "enabled": true, "version": "1.0.0"},
			map[string]any{"id": "public-share", "name": "Partage public Drive", "description": "Liens publics et partages externes.", "enabled": true, "version": "1.0.0"},
			map[string]any{"id": "office-editor", "name": "Édition OnlyOffice", "description": "Édition collaborative de documents.", "enabled": false, "version": "1.0.0"},
		},
		"integrations": []any{
			map[string]any{"id": "authentik", "name": "Authentik", "description": "SSO, groupes et provisionnement des comptes.", "enabled": true, "configured": false},
			map[string]any{"id": "nextcloud", "name": "Nextcloud", "description": "Drive, agenda, contacts et Talk.", "enabled": false, "configured": false},
			map[string]any{"id": "onlyoffice", "name": "OnlyOffice", "description": "Édition de documents dans le navigateur.", "enabled": false, "configured": false},
			map[string]any{"id": "smtp", "name": "Mailing unifié", "description": "SMTP pour notifications suite (partages, mentions).", "enabled": false, "configured": false},
		},
	}
}

func (s *Service) loadOrgPolicyRaw(ctx context.Context) (map[string]any, time.Time, string, error) {
	var raw []byte
	var updatedAt time.Time
	var updatedBy string
	err := s.db.QueryRow(ctx, `
		SELECT settings, updated_at, updated_by FROM org_settings WHERE id = $1
	`, orgSettingsSingletonID).Scan(&raw, &updatedAt, &updatedBy)
	if err != nil {
		if err == pgx.ErrNoRows {
			return map[string]any{}, time.Time{}, "", nil
		}
		return nil, time.Time{}, "", err
	}
	stored := map[string]any{}
	if len(raw) > 0 {
		if err := json.Unmarshal(raw, &stored); err != nil {
			return nil, time.Time{}, "", err
		}
	}
	return stored, updatedAt, updatedBy, nil
}

func mergeMaps(base, patch map[string]any) map[string]any {
	if base == nil {
		base = map[string]any{}
	}
	out := make(map[string]any, len(base)+len(patch))
	for k, v := range base {
		out[k] = v
	}
	for k, patchVal := range patch {
		if patchVal == nil {
			continue
		}
		baseVal, ok := out[k]
		if !ok {
			out[k] = patchVal
			continue
		}
		baseMap, baseOK := baseVal.(map[string]any)
		patchMap, patchOK := patchVal.(map[string]any)
		if baseOK && patchOK {
			out[k] = mergeMaps(baseMap, patchMap)
			continue
		}
		out[k] = patchVal
	}
	return out
}

func mergeOrgSecrets(existing, patch map[string]any) map[string]any {
	merged := mergeMaps(existing, patch)
	secretPaths := []struct {
		section string
		key     string
	}{
		{"nextcloud", "admin_password"},
		{"mailing", "smtp_password"},
		{"onlyoffice", "jwt_secret"},
		{"search", "meilisearch_api_key"},
		{"search", "typesense_api_key"},
		{"file_policies", "virustotal_api_key"},
	}
	for _, p := range secretPaths {
		patchSection, _ := patch[p.section].(map[string]any)
		if patchSection == nil {
			continue
		}
		val, _ := patchSection[p.key].(string)
		if strings.TrimSpace(val) == "" {
			if existingSection, ok := existing[p.section].(map[string]any); ok {
				if existingVal, ok := existingSection[p.key].(string); ok && existingVal != "" {
					mergedSection, _ := merged[p.section].(map[string]any)
					if mergedSection == nil {
						mergedSection = map[string]any{}
					}
					mergedSection[p.key] = existingVal
					merged[p.section] = mergedSection
				}
			}
		}
	}
	if patchLLM, ok := patch["llm"].(map[string]any); ok {
		mergeLLMProviderSecrets(existing, patchLLM, merged)
	}
	if patchSearch, ok := patch["search"].(map[string]any); ok {
		if patchWS, ok := patchSearch["web_search"].(map[string]any); ok {
			mergeSearchProviderSecrets(existing, patchWS, merged)
		}
	}
	if patchIDP, ok := patch["identity_providers"].(map[string]any); ok {
		mergeIdentityProviderSecrets(existing, patchIDP, merged)
	}
	return merged
}

func mergeLLMProviderSecrets(existing, patchLLM, merged map[string]any) {
	patchProviders, _ := patchLLM["providers"].([]any)
	if len(patchProviders) == 0 {
		return
	}
	existingLLM, _ := existing["llm"].(map[string]any)
	existingProviders, _ := existingLLM["providers"].([]any)
	mergedLLM, _ := merged["llm"].(map[string]any)
	if mergedLLM == nil {
		return
	}
	mergedProviders := make([]any, len(patchProviders))
	for i, item := range patchProviders {
		pm, ok := item.(map[string]any)
		if !ok {
			mergedProviders[i] = item
			continue
		}
		id, _ := pm["id"].(string)
		apiKey, _ := pm["api_key"].(string)
		if strings.TrimSpace(apiKey) == "" && id != "" {
			for _, ep := range existingProviders {
				em, ok := ep.(map[string]any)
				if !ok {
					continue
				}
				if eid, _ := em["id"].(string); eid == id {
					if ek, ok := em["api_key"].(string); ok && ek != "" {
						pm["api_key"] = ek
					}
				}
			}
		}
		mergedProviders[i] = pm
	}
	mergedLLM["providers"] = mergedProviders
	merged["llm"] = mergedLLM
}

func mergeSearchProviderSecrets(existing map[string]any, patchWS map[string]any, merged map[string]any) {
	patchProviders, _ := patchWS["providers"].([]any)
	if len(patchProviders) == 0 {
		return
	}
	existingSearch, _ := existing["search"].(map[string]any)
	existingWS, _ := existingSearch["web_search"].(map[string]any)
	existingProviders, _ := existingWS["providers"].([]any)
	mergedSearch, _ := merged["search"].(map[string]any)
	if mergedSearch == nil {
		return
	}
	mergedWS, _ := mergedSearch["web_search"].(map[string]any)
	if mergedWS == nil {
		mergedWS = map[string]any{}
	}
	mergedProviders := make([]any, len(patchProviders))
	for i, item := range patchProviders {
		pm, ok := item.(map[string]any)
		if !ok {
			mergedProviders[i] = item
			continue
		}
		id, _ := pm["id"].(string)
		apiKey, _ := pm["api_key"].(string)
		if strings.TrimSpace(apiKey) == "" && id != "" {
			for _, ep := range existingProviders {
				em, ok := ep.(map[string]any)
				if !ok {
					continue
				}
				if eid, _ := em["id"].(string); eid == id {
					if ek, ok := em["api_key"].(string); ok && ek != "" {
						pm["api_key"] = ek
					}
				}
			}
		}
		mergedProviders[i] = pm
	}
	mergedWS["providers"] = mergedProviders
	mergedSearch["web_search"] = mergedWS
	merged["search"] = mergedSearch
}

func mergeIdentityProviderSecrets(existing, patchIDP, merged map[string]any) {
	patchProviders, _ := patchIDP["providers"].([]any)
	if len(patchProviders) == 0 {
		return
	}
	existingIDP, _ := existing["identity_providers"].(map[string]any)
	existingProviders, _ := existingIDP["providers"].([]any)
	mergedIDP, _ := merged["identity_providers"].(map[string]any)
	if mergedIDP == nil {
		return
	}
	mergedProviders := make([]any, len(patchProviders))
	for i, item := range patchProviders {
		pm, ok := item.(map[string]any)
		if !ok {
			mergedProviders[i] = item
			continue
		}
		id, _ := pm["id"].(string)
		for _, secretPath := range []struct{ section, key string }{
			{"oauth", "client_secret"},
			{"ldap", "bind_password"},
			{"saml", "signing_cert"},
		} {
			mergeProviderSecretField(pm, existingProviders, id, secretPath.section, secretPath.key)
		}
		mergedProviders[i] = pm
	}
	mergedIDP["providers"] = mergedProviders
	merged["identity_providers"] = mergedIDP
}

func mergeProviderSecretField(pm map[string]any, existingProviders []any, id, section, key string) {
	nested, _ := pm[section].(map[string]any)
	if nested == nil {
		return
	}
	val, _ := nested[key].(string)
	if strings.TrimSpace(val) != "" || id == "" {
		return
	}
	for _, ep := range existingProviders {
		em, ok := ep.(map[string]any)
		if !ok {
			continue
		}
		if eid, _ := em["id"].(string); eid != id {
			continue
		}
		esec, _ := em[section].(map[string]any)
		if esec == nil {
			continue
		}
		if ek, ok := esec[key].(string); ok && ek != "" {
			nested[key] = ek
		}
	}
}

func maskOrgPolicy(policy map[string]any) map[string]any {
	cloned := deepCloneMap(policy)
	maskStringField(cloned, "nextcloud", "admin_password")
	maskStringField(cloned, "mailing", "smtp_password")
	maskStringField(cloned, "onlyoffice", "jwt_secret")
	maskStringField(cloned, "search", "meilisearch_api_key")
	maskStringField(cloned, "search", "typesense_api_key")
	maskStringField(cloned, "file_policies", "virustotal_api_key")
	if llm, ok := cloned["llm"].(map[string]any); ok {
		if providers, ok := llm["providers"].([]any); ok {
			for i, p := range providers {
				pm, ok := p.(map[string]any)
				if !ok {
					continue
				}
				if v, _ := pm["api_key"].(string); strings.TrimSpace(v) != "" {
					pm["api_key"] = ""
				}
				providers[i] = pm
			}
			llm["providers"] = providers
		}
	}
	if search, ok := cloned["search"].(map[string]any); ok {
		if ws, ok := search["web_search"].(map[string]any); ok {
			if providers, ok := ws["providers"].([]any); ok {
				for i, p := range providers {
					pm, ok := p.(map[string]any)
					if !ok {
						continue
					}
					if v, _ := pm["api_key"].(string); strings.TrimSpace(v) != "" {
						pm["api_key"] = ""
					}
					providers[i] = pm
				}
				ws["providers"] = providers
			}
		}
	}
	maskIdentityProviderSecrets(cloned)
	return cloned
}

func maskIdentityProviderSecrets(policy map[string]any) {
	idp, ok := policy["identity_providers"].(map[string]any)
	if !ok {
		return
	}
	providers, ok := idp["providers"].([]any)
	if !ok {
		return
	}
	for i, item := range providers {
		pm, ok := item.(map[string]any)
		if !ok {
			continue
		}
		for _, section := range []string{"oauth", "ldap", "saml"} {
			nested, ok := pm[section].(map[string]any)
			if !ok {
				continue
			}
			for _, key := range []string{"client_secret", "bind_password", "signing_cert"} {
				if v, _ := nested[key].(string); strings.TrimSpace(v) != "" {
					nested[key] = ""
				}
			}
			pm[section] = nested
		}
		providers[i] = pm
	}
	idp["providers"] = providers
}

func maskStringField(m map[string]any, section, key string) {
	sec, ok := m[section].(map[string]any)
	if !ok {
		return
	}
	if v, _ := sec[key].(string); strings.TrimSpace(v) != "" {
		sec[key] = ""
	}
}

func deepCloneMap(in map[string]any) map[string]any {
	raw, _ := json.Marshal(in)
	out := map[string]any{}
	_ = json.Unmarshal(raw, &out)
	return out
}

func secretConfigured(policy map[string]any, section, key string) bool {
	sec, ok := policy[section].(map[string]any)
	if !ok {
		return false
	}
	v, _ := sec[key].(string)
	return strings.TrimSpace(v) != ""
}

func buildOrgSecretsStatus(policy map[string]any, cfg *config.Config) map[string]any {
	secrets := map[string]any{
		"nextcloud_admin_password": map[string]any{
			"configured": secretConfigured(policy, "nextcloud", "admin_password") || strings.TrimSpace(cfg.NCAdminPass) != "",
		},
		"mailing_smtp_password": map[string]any{
			"configured": secretConfigured(policy, "mailing", "smtp_password"),
		},
		"onlyoffice_jwt_secret": map[string]any{
			"configured": secretConfigured(policy, "onlyoffice", "jwt_secret") || strings.TrimSpace(cfg.OnlyOfficeJWTSecret) != "",
		},
		"meilisearch_api_key": map[string]any{
			"configured": secretConfigured(policy, "search", "meilisearch_api_key") || strings.TrimSpace(cfg.MeilisearchKey) != "",
		},
		"typesense_api_key": map[string]any{
			"configured": secretConfigured(policy, "search", "typesense_api_key") || strings.TrimSpace(cfg.TypesenseKey) != "",
		},
		"virustotal_api_key": map[string]any{
			"configured": secretConfigured(policy, "file_policies", "virustotal_api_key") || strings.TrimSpace(cfg.VirusTotalAPIKey) != "",
		},
	}
	if idpSecrets := buildIdentityProviderSecretsStatus(policy); len(idpSecrets) > 0 {
		secrets["identity_providers"] = idpSecrets
	}
	return secrets
}

func buildIdentityProviderSecretsStatus(policy map[string]any) map[string]any {
	idp, ok := policy["identity_providers"].(map[string]any)
	if !ok {
		return nil
	}
	providers, ok := idp["providers"].([]any)
	if !ok {
		return nil
	}
	out := make(map[string]any)
	for _, item := range providers {
		pm, ok := item.(map[string]any)
		if !ok {
			continue
		}
		id, _ := pm["id"].(string)
		if id == "" {
			continue
		}
		entry := map[string]any{}
		if oauth, ok := pm["oauth"].(map[string]any); ok {
			if v, _ := oauth["client_secret"].(string); strings.TrimSpace(v) != "" {
				entry["oauth_client_secret"] = map[string]any{"configured": true}
			}
		}
		if ldap, ok := pm["ldap"].(map[string]any); ok {
			if v, _ := ldap["bind_password"].(string); strings.TrimSpace(v) != "" {
				entry["ldap_bind_password"] = map[string]any{"configured": true}
			}
		}
		if saml, ok := pm["saml"].(map[string]any); ok {
			if v, _ := saml["signing_cert"].(string); strings.TrimSpace(v) != "" {
				entry["saml_signing_cert"] = map[string]any{"configured": true}
			}
		}
		if len(entry) > 0 {
			out[id] = entry
		}
	}
	return out
}

func buildOrgEffective(cfg *config.Config) map[string]any {
	authentikEnabled := strings.TrimSpace(cfg.AuthentikAPIToken) != "" || strings.TrimSpace(cfg.OIDCIssuer) != ""
	publicBase := authentik.AuthentikPublicBaseURL(cfg.AuthentikAPIURL, cfg.AuthentikPublicHTTPS)
	return map[string]any{
		"authentik": map[string]any{
			"enabled":   authentikEnabled,
			"api_url":   cfg.AuthentikAPIURL,
			"client_id": cfg.OIDCClientID,
			"issuer":    cfg.OIDCIssuer,
		},
		"identity_providers": map[string]any{
			"authentik_public_url": publicBase,
			"oauth_redirect_template": publicBase + "/source/oauth/callback/{slug}/",
		},
		"nextcloud": map[string]any{
			"enabled":   cfg.NextcloudEnabled,
			"base_url":  firstNonEmpty(cfg.NextcloudPublicURL, cfg.NextcloudURL),
			"admin_user": cfg.NCAdminUser,
		},
		"onlyoffice": map[string]any{
			"enabled":             cfg.OnlyOfficeEnabled,
			"document_server_url": firstNonEmpty(cfg.OnlyOfficePublicURL, cfg.OnlyOfficeURL),
		},
		"search": map[string]any{
			"suite_engine":    cfg.SearchEngine,
			"meilisearch_url": cfg.MeilisearchURL,
			"typesense_url":   cfg.TypesenseURL,
		},
		"immich": map[string]any{
			"enabled": cfg.ImmichEnabled,
			"api_url": cfg.ImmichAPIURL,
		},
		"jitsi": map[string]any{
			"enabled":    cfg.JitsiEnabled,
			"public_url": cfg.JitsiPublicURL,
		},
	}
}

func firstNonEmpty(values ...string) string {
	for _, v := range values {
		if strings.TrimSpace(v) != "" {
			return v
		}
	}
	return ""
}

func (s *Service) GetOrgSettings(ctx context.Context) (map[string]any, error) {
	stored, updatedAt, updatedBy, err := s.loadOrgPolicyRaw(ctx)
	if err != nil {
		return nil, err
	}
	policy := mergeMaps(defaultOrgPolicy(), stored)
	masked := maskOrgPolicy(policy)
	return map[string]any{
		"policy":         masked,
		"effective":      buildOrgEffective(s.cfg),
		"secrets":        buildOrgSecretsStatus(policy, s.cfg),
		"env_vars":       buildOrgEnvVars(),
		"deploy_locked":  buildOrgDeployLocked(s.cfg),
		"updated_at":     updatedAt.UTC().Format(time.RFC3339),
		"updated_by":     updatedBy,
	}, nil
}

func (s *Service) PutOrgSettings(ctx context.Context, actorSub string, patch map[string]any) (map[string]any, error) {
	storedBefore, _, _, err := s.loadOrgPolicyRaw(ctx)
	if err != nil {
		return nil, err
	}
	merged := mergeOrgSecrets(storedBefore, patch)
	raw, err := json.Marshal(merged)
	if err != nil {
		return nil, err
	}
	_, err = s.db.Exec(ctx, `
		INSERT INTO org_settings (id, settings, updated_at, updated_by)
		VALUES ($1, $2, NOW(), $3)
		ON CONFLICT (id) DO UPDATE SET
			settings = EXCLUDED.settings,
			updated_at = NOW(),
			updated_by = EXCLUDED.updated_by
	`, orgSettingsSingletonID, raw, actorSub)
	if err != nil {
		return nil, err
	}
	s.logAudit(ctx, actorSub, "update_org_settings", map[string]any{
		"sections": mapKeys(patch),
	})

	if _, ok := patch["identity_providers"]; ok {
		removed := authentik.RemovedIdentityProviders(storedBefore, merged)
		syncer := authentik.NewSourceSyncer(s.db, s.cfg)
		idpSection, _ := merged["identity_providers"].(map[string]any)
		if err := syncer.SyncSectionWithRemoved(ctx, idpSection, removed); err != nil {
			s.logger.Warn("identity provider sync failed", "error", err)
		}
	}

	return s.GetOrgSettings(ctx)
}

func mapKeys(m map[string]any) []string {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

const gibBytes = int64(1024 * 1024 * 1024)

func policyGibValue(v any, fallback int64) int64 {
	switch n := v.(type) {
	case float64:
		if n > 0 {
			return int64(n * float64(gibBytes))
		}
	case int:
		if n > 0 {
			return int64(n) * gibBytes
		}
	case int64:
		if n > 0 {
			return n * gibBytes
		}
	}
	return fallback * gibBytes
}

func (s *Service) applyOrgDefaultQuotas(ctx context.Context, userID string) error {
	stored, _, _, err := s.loadOrgPolicyRaw(ctx)
	if err != nil {
		return err
	}
	policy := mergeMaps(defaultOrgPolicy(), stored)
	storage, _ := policy["storage_quotas"].(map[string]any)
	const defaultGib = int64(5)
	mail := policyGibValue(storage["default_mail_gib"], defaultGib)
	drive := policyGibValue(storage["default_drive_gib"], defaultGib)
	photos := policyGibValue(storage["default_photos_gib"], defaultGib)
	_, err = s.db.Exec(ctx, `
		INSERT INTO settings (user_id, preferences)
		VALUES (
			$1,
			jsonb_build_object(
				'mail_max_storage_bytes', $2::text,
				'drive_max_storage_bytes', $3::text,
				'photos_max_storage_bytes', $4::text
			)
		)
		ON CONFLICT (user_id) DO NOTHING
	`, userID, mail, drive, photos)
	return err
}
