package api

import (
	"encoding/json"
	"net/http"
	"strconv"

	"archivmail/internal/audit"
	"archivmail/internal/ldapauth"
	ldapcfg "archivmail/internal/ldapconfig"
)

// ── LDAP handlers ────────────────────────────────────────────────────────────

func (s *Server) handleGetLDAP(w http.ResponseWriter, r *http.Request) {
	if s.ldapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "ldap store not available")
		return
	}
	cfg, err := s.ldapStore.Get(r.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to load ldap config")
		return
	}
	if cfg == nil {
		writeError(w, http.StatusNotFound, "no ldap config")
		return
	}
	writeJSON(w, http.StatusOK, cfg)
}

func (s *Server) handleSaveLDAP(w http.ResponseWriter, r *http.Request) {
	if s.ldapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "ldap store not available")
		return
	}
	var cfg ldapcfg.LDAPConfig
	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	sess := sessionFromCtx(r.Context())
	if err := s.ldapStore.Save(r.Context(), cfg, sess.Username); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to save ldap config")
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: "ldap_config_saved",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "LDAP-Konfiguration gespeichert",
	})

	// Return the saved config (with masked password)
	saved, err := s.ldapStore.Get(r.Context())
	if err != nil || saved == nil {
		writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
		return
	}
	writeJSON(w, http.StatusOK, saved)
}

func (s *Server) handleDeleteLDAP(w http.ResponseWriter, r *http.Request) {
	if s.ldapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "ldap store not available")
		return
	}
	if err := s.ldapStore.Delete(r.Context()); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to delete ldap config")
		return
	}

	sess := sessionFromCtx(r.Context())
	s.audlog.Log(audit.Entry{
		EventType: "ldap_config_deleted",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "LDAP-Konfiguration gelöscht",
	})

	w.WriteHeader(http.StatusNoContent)
}

func (s *Server) handleTestLDAP(w http.ResponseWriter, r *http.Request) {
	if s.ldapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "ldap store not available")
		return
	}

	var body struct {
		UseSaved bool `json:"use_saved"`
		ldapcfg.LDAPConfig
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	var testCfg ldapauth.Config

	if body.UseSaved {
		saved, err := s.ldapStore.GetWithPassword(r.Context())
		if err != nil || saved == nil {
			writeError(w, http.StatusNotFound, "no ldap config saved")
			return
		}
		testCfg = ldapauth.Config{
			URL:           saved.URL,
			BindDN:        saved.BindDN,
			BindPassword:  saved.BindPassword,
			BaseDN:        saved.BaseDN,
			UserFilter:    saved.UserFilter,
			TLS:           saved.TLS,
			TLSSkipVerify: saved.TLSSkipVerify,
		}
	} else {
		testCfg = ldapauth.Config{
			URL:           body.URL,
			BindDN:        body.BindDN,
			BindPassword:  body.BindPassword,
			BaseDN:        body.BaseDN,
			UserFilter:    body.UserFilter,
			TLS:           body.TLS,
			TLSSkipVerify: body.TLSSkipVerify,
		}
	}

	result := ldapauth.TestConnection(testCfg)

	sess := sessionFromCtx(r.Context())
	s.audlog.Log(audit.Entry{
		EventType: "ldap_connection_test",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   result.OK,
		Detail:    result.Message,
	})

	writeJSON(w, http.StatusOK, result)
}

// ── PROJ-23: Per-Tenant LDAP — domain_admin handlers (own tenant) ────────────

func (s *Server) handleGetTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	sess := sessionFromCtx(r.Context())
	if sess.TenantID == nil {
		writeError(w, http.StatusBadRequest, "no tenant context")
		return
	}
	cfg, err := s.tenantLdapStore.Get(r.Context(), *sess.TenantID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to load tenant ldap config")
		return
	}
	if cfg == nil {
		writeError(w, http.StatusNotFound, "no tenant ldap config")
		return
	}
	writeJSON(w, http.StatusOK, cfg)
}

func (s *Server) handleSaveTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	sess := sessionFromCtx(r.Context())
	if sess.TenantID == nil {
		writeError(w, http.StatusBadRequest, "no tenant context")
		return
	}

	var cfg ldapcfg.TenantLDAPConfig
	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}
	cfg.TenantID = *sess.TenantID

	// BUG-1 fix: domain_admin may only assign user/auditor roles — prevent privilege escalation
	// via LDAP default_role or group_mappings even when bypassing the frontend.
	allowedForTenantAdmin := map[string]bool{"user": true, "auditor": true}
	if cfg.DefaultRole != "" && !allowedForTenantAdmin[cfg.DefaultRole] {
		writeError(w, http.StatusForbidden, "role not allowed for tenant LDAP config")
		return
	}
	for _, gm := range cfg.GroupMappings {
		if !allowedForTenantAdmin[gm.Role] {
			writeError(w, http.StatusForbidden, "group mapping role not allowed")
			return
		}
	}

	if err := s.tenantLdapStore.Save(r.Context(), cfg, sess.Username); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to save tenant ldap config")
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_config_saved",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "Mandant-LDAP-Konfiguration gespeichert",
	})

	saved, err := s.tenantLdapStore.Get(r.Context(), *sess.TenantID)
	if err != nil || saved == nil {
		writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
		return
	}
	writeJSON(w, http.StatusOK, saved)
}

func (s *Server) handleDeleteTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	sess := sessionFromCtx(r.Context())
	if sess.TenantID == nil {
		writeError(w, http.StatusBadRequest, "no tenant context")
		return
	}
	if err := s.tenantLdapStore.Delete(r.Context(), *sess.TenantID); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to delete tenant ldap config")
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_config_deleted",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "Mandant-LDAP-Konfiguration gelöscht",
	})

	w.WriteHeader(http.StatusNoContent)
}

func (s *Server) handleTestTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	sess := sessionFromCtx(r.Context())
	if sess.TenantID == nil {
		writeError(w, http.StatusBadRequest, "no tenant context")
		return
	}

	var body struct {
		UseSaved bool `json:"use_saved"`
		ldapcfg.TenantLDAPConfig
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	testCfg := s.buildTenantTestConfig(r, body.UseSaved, *sess.TenantID, body.TenantLDAPConfig)
	if testCfg == nil {
		writeError(w, http.StatusNotFound, "no tenant ldap config saved")
		return
	}

	result := ldapauth.TestConnection(*testCfg)

	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_connection_test",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   result.OK,
		Detail:    result.Message,
	})

	writeJSON(w, http.StatusOK, result)
}

// ── PROJ-23: Per-Tenant LDAP — superadmin handlers (arbitrary tenant) ────────

func (s *Server) handleAdminGetTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	id, err := parseTenantID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid tenant id")
		return
	}
	cfg, err := s.tenantLdapStore.Get(r.Context(), id)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to load tenant ldap config")
		return
	}
	if cfg == nil {
		writeError(w, http.StatusNotFound, "no tenant ldap config")
		return
	}
	writeJSON(w, http.StatusOK, cfg)
}

func (s *Server) handleAdminSaveTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	id, err := parseTenantID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid tenant id")
		return
	}

	var cfg ldapcfg.TenantLDAPConfig
	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}
	cfg.TenantID = id

	// superadmin may assign up to domain_admin in group mappings — not superadmin itself.
	allowedForSuperAdmin := map[string]bool{"user": true, "auditor": true, "domain_admin": true}
	if cfg.DefaultRole != "" && !allowedForSuperAdmin[cfg.DefaultRole] {
		writeError(w, http.StatusForbidden, "role not allowed for tenant LDAP config")
		return
	}
	for _, gm := range cfg.GroupMappings {
		if !allowedForSuperAdmin[gm.Role] {
			writeError(w, http.StatusForbidden, "group mapping role not allowed")
			return
		}
	}

	sess := sessionFromCtx(r.Context())
	if err := s.tenantLdapStore.Save(r.Context(), cfg, sess.Username); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to save tenant ldap config")
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_config_saved",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "Mandant-LDAP-Konfiguration gespeichert (tenant " + strconv.FormatInt(id, 10) + ")",
	})

	saved, err := s.tenantLdapStore.Get(r.Context(), id)
	if err != nil || saved == nil {
		writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
		return
	}
	writeJSON(w, http.StatusOK, saved)
}

func (s *Server) handleAdminDeleteTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	id, err := parseTenantID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid tenant id")
		return
	}
	if err := s.tenantLdapStore.Delete(r.Context(), id); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to delete tenant ldap config")
		return
	}

	sess := sessionFromCtx(r.Context())
	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_config_deleted",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "Mandant-LDAP-Konfiguration gelöscht (tenant " + strconv.FormatInt(id, 10) + ")",
	})

	w.WriteHeader(http.StatusNoContent)
}

func (s *Server) handleAdminTestTenantLDAP(w http.ResponseWriter, r *http.Request) {
	if s.tenantLdapStore == nil {
		writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
		return
	}
	id, err := parseTenantID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid tenant id")
		return
	}

	var body struct {
		UseSaved bool `json:"use_saved"`
		ldapcfg.TenantLDAPConfig
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	testCfg := s.buildTenantTestConfig(r, body.UseSaved, id, body.TenantLDAPConfig)
	if testCfg == nil {
		writeError(w, http.StatusNotFound, "no tenant ldap config saved")
		return
	}

	result := ldapauth.TestConnection(*testCfg)

	sess := sessionFromCtx(r.Context())
	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_connection_test",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   result.OK,
		Detail:    result.Message + " (tenant " + strconv.FormatInt(id, 10) + ")",
	})

	writeJSON(w, http.StatusOK, result)
}
