package api

import (
	"fmt"
	"net/http"

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

// syncResult is the JSON response returned by LDAP sync endpoints.
type syncResult struct {
	Synced int      `json:"synced"`
	Errors []string `json:"errors"`
}

// handleSyncTenantLDAP imports all LDAP users into the tenant (domain_admin).
func (s *Server) handleSyncTenantLDAP(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
	}
	res := s.doSyncTenantLDAP(r, *sess.TenantID)
	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_sync",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   len(res.Errors) == 0,
		Detail:    fmt.Sprintf("%d Benutzer synchronisiert", res.Synced),
	})
	writeJSON(w, http.StatusOK, res)
}

// handleAdminSyncTenantLDAP imports all LDAP users into any tenant (superadmin).
func (s *Server) handleAdminSyncTenantLDAP(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
	}
	res := s.doSyncTenantLDAP(r, id)
	sess := sessionFromCtx(r.Context())
	s.audlog.Log(audit.Entry{
		EventType: "tenant_ldap_sync",
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   len(res.Errors) == 0,
		Detail:    fmt.Sprintf("%d Benutzer synchronisiert (tenant %d)", res.Synced, id),
	})
	writeJSON(w, http.StatusOK, res)
}

// doSyncTenantLDAP is the shared sync logic: fetch all LDAP users and upsert
// them into the local userstore with source='ldap'.
func (s *Server) doSyncTenantLDAP(r *http.Request, tenantID int64) syncResult {
	saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID)
	if err != nil || saved == nil {
		return syncResult{Errors: []string{"keine LDAP-Konfiguration vorhanden"}}
	}

	cfg := ldapauth.Config{
		URL:           saved.URL,
		BindDN:        saved.BindDN,
		BindPassword:  saved.BindPassword,
		BaseDN:        saved.BaseDN,
		UserFilter:    saved.UserFilter,
		TLS:           saved.TLS,
		TLSSkipVerify: saved.TLSSkipVerify,
	}

	ldapUsers, err := ldapauth.FetchUsers(cfg)
	if err != nil {
		return syncResult{Errors: []string{err.Error()}}
	}

	defaultRole := saved.DefaultRole
	if defaultRole == "" {
		defaultRole = "user"
	}

	var res syncResult
	res.Errors = []string{}
	for _, u := range ldapUsers {
		mail := u.Mail
		if mail == "" {
			mail = u.UID + "@ldap.local"
		}
		if _, uErr := s.users.UpsertLDAPUser(u.UID, mail, defaultRole, &tenantID); uErr != nil {
			res.Errors = append(res.Errors, fmt.Sprintf("%s: %s", u.UID, uErr.Error()))
		} else {
			res.Synced++
		}
	}
	return res
}

// buildTenantTestConfig constructs an ldapauth.Config for testing, either from
// the saved tenant config or from the provided request body.
func (s *Server) buildTenantTestConfig(r *http.Request, useSaved bool, tenantID int64, provided ldapcfg.TenantLDAPConfig) *ldapauth.Config {
	if useSaved {
		saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID)
		if err != nil || saved == nil {
			return nil
		}
		return &ldapauth.Config{
			URL:           saved.URL,
			BindDN:        saved.BindDN,
			BindPassword:  saved.BindPassword,
			BaseDN:        saved.BaseDN,
			UserFilter:    saved.UserFilter,
			TLS:           saved.TLS,
			TLSSkipVerify: saved.TLSSkipVerify,
		}
	}
	return &ldapauth.Config{
		URL:           provided.URL,
		BindDN:        provided.BindDN,
		BindPassword:  provided.BindPassword,
		BaseDN:        provided.BaseDN,
		UserFilter:    provided.UserFilter,
		TLS:           provided.TLS,
		TLSSkipVerify: provided.TLSSkipVerify,
	}
}
