package mail

import (
	"errors"
	"net/http"
	"strings"

	"github.com/jackc/pgx/v5"

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/api/middleware"
	"github.com/ultisuite/ulti-backend/internal/api/query"
	"github.com/ultisuite/ulti-backend/internal/mail/hosted"
)

func (h *Handler) SetHostedService(svc *hosted.Service) {
	if s, ok := h.svc.(*Service); ok {
		s.SetHostedService(svc)
	}
}

func (h *Handler) SetHostedPlatformDomain(domain string) {
	h.hostedPlatformDomain = strings.ToLower(strings.TrimSpace(domain))
}

func (h *Handler) CheckAddressAvailability(w http.ResponseWriter, r *http.Request) {
	svc := h.hostedService()
	if svc == nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"available": true, "reason": "hosted_mail_disabled"})
		return
	}
	local := strings.TrimSpace(r.URL.Query().Get("local"))
	domain := strings.TrimSpace(r.URL.Query().Get("domain"))
	if domain == "" {
		domain = strings.TrimSpace(r.URL.Query().Get("domain_name"))
	}
	if domain == "" {
		domain = h.hostedPlatformDomain
	}
	if local == "" || domain == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "local", Message: "local and domain required",
		}))
		return
	}
	available, err := svc.IsAddressAvailable(r.Context(), domain, local)
	if err != nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"available": false, "reason": err.Error()})
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"available": available})
}

func (h *Handler) GetHostedMailStatus(w http.ResponseWriter, r *http.Request) {
	svc := h.hostedService()
	if svc == nil || !svc.Available() {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"enabled": false})
		return
	}

	ctx := r.Context()
	domainName := h.hostedPlatformDomain
	domainStatus := ""
	if domain, err := svc.GetPlatformDomain(ctx); err == nil {
		domainName = domain.Name
		domainStatus = domain.Status
	}
	if domainName == "" {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"enabled": false})
		return
	}

	resp := map[string]any{
		"enabled":         true,
		"platform_domain": domainName,
		"domain_status":   domainStatus,
		"endpoints":       svc.Endpoints(),
	}

	claims := middleware.ClaimsFromContext(ctx)
	if claims != nil {
		userID, err := h.svc.ResolveUserID(ctx, claims.Sub)
		if err == nil {
			if mailbox, err := svc.GetUserMailbox(ctx, userID); err == nil {
				resp["mailbox"] = mailbox
			} else if !errors.Is(err, pgx.ErrNoRows) {
				h.logger.Error("hosted mailbox lookup", "error", err)
			}
			accounts, listErr := h.svc.ListAccounts(ctx, claims.Sub, query.ListParams{})
			if listErr == nil {
				for _, acct := range accounts.Accounts {
					provider, _ := acct["provider"].(string)
					if provider == "hosted" {
						resp["hosted_mail_account_id"] = acct["id"]
						resp["hosted_mail_account_email"] = acct["email"]
						break
					}
				}
			}
		}
	}

	apiresponse.WriteJSON(w, http.StatusOK, resp)
}

type setupHostedMailboxRequest struct {
	LocalPart   string `json:"local_part"`
	Password    string `json:"password"`
	DisplayName string `json:"display_name"`
}

func (h *Handler) SetupHostedMailbox(w http.ResponseWriter, r *http.Request) {
	svc := h.hostedService()
	if svc == nil || !svc.Available() {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "hosted_mail_disabled", "hosted mail is not configured", nil)
		return
	}

	var req setupHostedMailboxRequest
	if err := apivalidate.DecodeJSON(w, r, 16*1024, &req); err != nil {
		return
	}
	localPart := strings.TrimSpace(req.LocalPart)
	password := req.Password
	if localPart == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(
			apivalidate.FieldDetail{Field: "local_part", Message: "local part required"},
		))
		return
	}
	if len(password) < 8 {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(
			apivalidate.FieldDetail{Field: "password", Message: "password must be at least 8 characters"},
		))
		return
	}

	domainName := h.hostedPlatformDomain
	if domain, err := svc.GetPlatformDomain(r.Context()); err == nil {
		domainName = domain.Name
	}
	if domainName == "" {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "hosted_mail_disabled", "platform mail domain not configured", nil)
		return
	}

	claims := middleware.ClaimsFromContext(r.Context())
	userID, err := h.svc.ResolveUserID(r.Context(), claims.Sub)
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "user not provisioned", nil)
		return
	}

	displayName := strings.TrimSpace(req.DisplayName)
	if displayName == "" {
		displayName = localPart
	}
	email := strings.ToLower(localPart + "@" + domainName)

	result, err := svc.EnsureMailboxProvisioned(r.Context(), hosted.ProvisionMailboxInput{
		UserID:      userID,
		Email:       email,
		DisplayName: displayName,
		Password:    password,
	})
	if err != nil {
		switch {
		case errors.Is(err, hosted.ErrAddressTaken):
			apiresponse.WriteError(w, r, http.StatusConflict, "address_taken", err.Error(), nil)
		case errors.Is(err, hosted.ErrInvalidLocalPart):
			apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(
				apivalidate.FieldDetail{Field: "local_part", Message: err.Error()},
			))
		case errors.Is(err, hosted.ErrDomainNotActive):
			apiresponse.WriteError(w, r, http.StatusConflict, "domain_not_active", err.Error(), nil)
		default:
			h.logger.Error("setup hosted mailbox", "error", err, "email", email)
			apiresponse.WriteError(w, r, http.StatusInternalServerError, apiresponse.CodeInternal, "failed to provision mailbox", nil)
		}
		return
	}

	if h.accountSync != nil && result.MailAccountID != "" {
		if syncErr := h.accountSync.SyncAccountForUser(r.Context(), claims.Sub, result.MailAccountID); syncErr != nil {
			h.logger.Warn("hosted mailbox initial sync", "error", syncErr, "account_id", result.MailAccountID)
		}
	}

	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{
		"mailbox":         result.Mailbox,
		"mail_account_id": result.MailAccountID,
		"email":           email,
	})
}

func (h *Handler) hostedService() *hosted.Service {
	if s, ok := h.svc.(*Service); ok {
		return s.HostedService()
	}
	return nil
}
