package admin

import (
	"encoding/csv"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/api/query"
	migr "github.com/ultisuite/ulti-backend/internal/migration"
)

const maxAdminMailRequestBody = 1 << 20

func (h *Handler) registerMailAdminRoutes(r chi.Router, read, write func(http.Handler) http.Handler) {
	if h.svc.hosted == nil && h.svc.migration == nil {
		return
	}
	r.Route("/mail", func(r chi.Router) {
		if h.svc.hosted != nil {
			r.With(read).Get("/domains", h.ListMailDomains)
			r.With(write).Post("/domains", h.CreateMailDomain)
			r.With(read).Get("/domains/{domainID}", h.GetMailDomain)
			r.With(write).Post("/domains/{domainID}/verify-txt", h.VerifyMailDomainTXT)
			r.With(write).Post("/domains/{domainID}/verify-mx", h.VerifyMailDomainMX)
		}
	})
	r.Route("/migration", func(r chi.Router) {
		if h.svc.migration == nil {
			return
		}
		r.With(read).Get("/projects", h.ListMigrationProjects)
		r.With(write).Post("/projects", h.CreateMigrationProject)
		r.With(write).Post("/projects/{projectID}/activate", h.ActivateMigrationProject)
		r.With(read).Get("/projects/{projectID}/cutover-dns", h.PreflightMigrationCutoverDNS)
		r.With(write).Post("/projects/{projectID}/cutover", h.StartMigrationCutover)
		r.With(write).Post("/projects/{projectID}/invites", h.CreateMigrationInvite)
		r.With(write).Post("/projects/{projectID}/invites/import", h.ImportMigrationInvites)
		r.With(read).Get("/projects/{projectID}/roster", h.ListMigrationRoster)
		r.With(write).Post("/projects/{projectID}/roster", h.ImportMigrationRoster)
		r.With(read).Get("/projects/{projectID}/jobs", h.ListMigrationProjectJobs)
		r.With(read).Get("/projects/{projectID}/jobs/{jobID}/audit", h.ListMigrationJobAudit)
		r.With(read).Get("/projects/{projectID}/jobs/{jobID}/audit/summary", h.MigrationJobAuditSummary)
		r.With(read).Get("/projects/{projectID}/jobs/{jobID}/audit/export", h.ExportMigrationJobAudit)
		r.With(read).Get("/projects/{projectID}/audit/export", h.ExportMigrationProjectAudit)
		r.With(write).Post("/projects/{projectID}/jobs/retry-failed", h.RetryMigrationFailedJobs)
		r.With(write).Post("/projects/{projectID}/jobs/{jobID}/retry", h.RetryMigrationJob)
		r.With(write).Post("/projects/{projectID}/jobs/{jobID}/reset-cursor", h.ResetMigrationJobCursor)
		r.With(write).Patch("/projects/{projectID}/shared-drive-mode", h.UpdateMigrationSharedDriveMode)
		r.With(read).Get("/projects/{projectID}/shared-drives", h.ListMigrationSharedDrives)
		r.With(write).Post("/projects/{projectID}/shared-drives/{driveID}/approve", h.ApproveMigrationSharedDrive)
		r.With(write).Post("/projects/{projectID}/shared-drives/{driveID}/reject", h.RejectMigrationSharedDrive)
		r.With(read).Get("/microsoft/admin-consent-url", h.MicrosoftMigrationAdminConsentURL)
		r.With(read).Get("/microsoft/admin-consents", h.ListMicrosoftAdminConsents)
	})
}

func (h *Handler) ListMailDomains(w http.ResponseWriter, r *http.Request) {
	rows, err := h.svc.hosted.ListDomains(r.Context())
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"domains": rows})
}

type createMailDomainRequest struct {
	Name string `json:"name"`
}

func (h *Handler) CreateMailDomain(w http.ResponseWriter, r *http.Request) {
	var req createMailDomainRequest
	if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
		return
	}
	row, err := h.svc.hosted.CreateDomain(r.Context(), req.Name, false)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusCreated, row)
}

func (h *Handler) GetMailDomain(w http.ResponseWriter, r *http.Request) {
	row, err := h.svc.hosted.GetDomain(r.Context(), chi.URLParam(r, "domainID"))
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}

func (h *Handler) VerifyMailDomainTXT(w http.ResponseWriter, r *http.Request) {
	domainID := chi.URLParam(r, "domainID")
	row, report, err := h.svc.hosted.VerifyDomainTXTRecord(r.Context(), domainID)
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusBadRequest, "dns_txt_not_verified", err.Error(), map[string]any{"dns": report})
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"domain": row, "dns": report})
}

func (h *Handler) VerifyMailDomainMX(w http.ResponseWriter, r *http.Request) {
	domainID := chi.URLParam(r, "domainID")
	expected := h.migrationCutoverConfig().ExpectedMXHosts
	row, report, err := h.svc.hosted.VerifyDomainMXRecord(r.Context(), domainID, expected)
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusBadRequest, "dns_mx_not_verified", err.Error(), map[string]any{"dns": report})
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"domain": row, "dns": report})
}

type createMigrationProjectRequest struct {
	Name           string `json:"name"`
	SourceProvider string `json:"source_provider"`
	DomainID       string `json:"domain_id"`
	AuthMode       string `json:"auth_mode"`
}

func (h *Handler) CreateMigrationProject(w http.ResponseWriter, r *http.Request) {
	var req createMigrationProjectRequest
	if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
		return
	}
	row, err := h.svc.migration.CreateProject(r.Context(), req.Name, req.SourceProvider, req.DomainID, req.AuthMode)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusCreated, row)
}

func (h *Handler) ListMigrationProjects(w http.ResponseWriter, r *http.Request) {
	rows, err := h.svc.migration.ListProjects(r.Context())
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"projects": rows})
}

func (h *Handler) ActivateMigrationProject(w http.ResponseWriter, r *http.Request) {
	row, err := h.svc.migration.ActivateProject(r.Context(), chi.URLParam(r, "projectID"))
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}

func (h *Handler) PreflightMigrationCutoverDNS(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	report, err := h.svc.migration.PreflightCutoverDNS(r.Context(), chi.URLParam(r, "projectID"), h.migrationCutoverConfig())
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"dns": report})
}

func (h *Handler) StartMigrationCutover(w http.ResponseWriter, r *http.Request) {
	result, err := h.svc.migration.StartCutover(r.Context(), chi.URLParam(r, "projectID"))
	if errors.Is(err, migr.ErrCutoverMXNotReady) {
		apiresponse.WriteError(w, r, http.StatusConflict, "migration_cutover_mx_not_ready", err.Error(), map[string]any{
			"dns":     result.DNS,
			"project": result.Project,
		})
		return
	}
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, result)
}

func (h *Handler) migrationCutoverConfig() migr.CutoverConfig {
	if h.svc.cfg == nil {
		return migr.CutoverConfig{}
	}
	return migr.CutoverConfig{
		ExpectedMXHosts: migr.ParseCutoverMXHosts(
			h.svc.cfg.MigrationCutoverMXHosts,
			h.svc.cfg.PlatformMailDomain,
			h.svc.cfg.StalwartIMAPHost,
		),
		RequireMX: h.svc.cfg.MigrationCutoverRequireMX,
	}
}

type createMigrationInviteRequest struct {
	Email           string   `json:"email"`
	AlternateEmails []string `json:"alternate_emails,omitempty"`
}

func (h *Handler) CreateMigrationInvite(w http.ResponseWriter, r *http.Request) {
	var req createMigrationInviteRequest
	if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
		return
	}
	row, err := h.svc.migration.CreateInvite(r.Context(), chi.URLParam(r, "projectID"), req.Email, req.AlternateEmails)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusCreated, row)
}

func (h *Handler) ImportMigrationInvites(w http.ResponseWriter, r *http.Request) {
	var emails []string
	contentType := r.Header.Get("Content-Type")
	if strings.Contains(contentType, "multipart/form-data") {
		file, _, err := r.FormFile("file")
		if err == nil {
			defer file.Close()
			reader := csv.NewReader(file)
			for {
				record, err := reader.Read()
				if err == io.EOF {
					break
				}
				if err != nil {
					apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
						Field: "file", Message: "invalid csv",
					}))
					return
				}
				if len(record) > 0 {
					emails = append(emails, record[0])
				}
			}
		}
	}
	if len(emails) == 0 {
		var body struct {
			Emails []string `json:"emails"`
		}
		if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &body); err != nil {
			return
		}
		emails = body.Emails
	}
	count, err := h.svc.migration.ImportInvites(r.Context(), chi.URLParam(r, "projectID"), emails)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"imported": count})
}

func (h *Handler) ListMigrationRoster(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	rows, err := h.svc.migration.ListRoster(r.Context(), chi.URLParam(r, "projectID"))
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	if rows == nil {
		rows = []migr.RosterEntry{}
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"roster": rows})
}

func (h *Handler) ImportMigrationRoster(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	projectID := chi.URLParam(r, "projectID")

	var inputs []migr.RosterRowInput
	contentType := r.Header.Get("Content-Type")
	if strings.Contains(contentType, "multipart/form-data") {
		file, _, err := r.FormFile("file")
		if err == nil {
			defer file.Close()
			parsed, err := migr.ParseRosterCSV(file)
			if err != nil {
				apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
					Field: "file", Message: err.Error(),
				}))
				return
			}
			inputs = parsed
		}
	}
	if len(inputs) == 0 {
		var body struct {
			CSV  string               `json:"csv"`
			Rows []migr.RosterRowInput `json:"rows"`
		}
		if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &body); err != nil {
			return
		}
		if len(body.Rows) > 0 {
			inputs = body.Rows
		} else if strings.TrimSpace(body.CSV) != "" {
			parsed, err := migr.ParseRosterCSV(strings.NewReader(body.CSV))
			if err != nil {
				apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
					Field: "csv", Message: err.Error(),
				}))
				return
			}
			inputs = parsed
		}
	}
	if len(inputs) == 0 {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "csv", Message: "roster csv or rows required",
		}))
		return
	}

	result, err := h.svc.migration.ImportRoster(r.Context(), projectID, inputs)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, result)
}

func (h *Handler) MicrosoftMigrationAdminConsentURL(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	consentURL, err := h.svc.migration.MicrosoftAdminConsentURL(
		r.URL.Query().Get("tenant"),
		r.URL.Query().Get("project_id"),
	)
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusBadRequest, "admin_consent_unavailable", err.Error(), nil)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"url": consentURL})
}

func (h *Handler) ListMicrosoftAdminConsents(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	rows, err := h.svc.migration.ListMicrosoftAdminConsents(r.Context())
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"consents": rows})
}

func (h *Handler) ListMigrationProjectJobs(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	rows, err := h.svc.migration.ListProjectJobs(r.Context(), chi.URLParam(r, "projectID"))
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"jobs": rows})
}

func (h *Handler) RetryMigrationJob(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	row, err := h.svc.migration.RetryJob(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "jobID"))
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_retryable", err.Error(), nil)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}

func (h *Handler) ResetMigrationJobCursor(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	row, err := h.svc.migration.ResetJobCursor(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "jobID"))
	if err != nil {
		status := http.StatusNotFound
		code := "migration_job_not_resettable"
		if strings.Contains(err.Error(), "running") {
			status = http.StatusConflict
		} else if strings.Contains(err.Error(), "not found") {
			code = "migration_job_not_found"
		}
		apiresponse.WriteError(w, r, status, code, err.Error(), nil)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}

func (h *Handler) RetryMigrationFailedJobs(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	count, err := h.svc.migration.RetryFailedJobs(r.Context(), chi.URLParam(r, "projectID"))
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"retried": count})
}

func (h *Handler) ListMigrationJobAudit(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	params, err := query.ParseListRequest(r)
	if err != nil {
		apivalidate.WriteQueryError(w, r, err)
		return
	}
	items, pagination, err := h.svc.migration.ListJobAudit(
		r.Context(),
		chi.URLParam(r, "projectID"),
		chi.URLParam(r, "jobID"),
		r.URL.Query().Get("status"),
		params,
	)
	if err != nil {
		if strings.Contains(err.Error(), "not found") {
			apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_found", err.Error(), nil)
			return
		}
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{
		"items":      items,
		"pagination": pagination,
	})
}

func (h *Handler) MigrationJobAuditSummary(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	summary, err := h.svc.migration.JobAuditSummary(
		r.Context(),
		chi.URLParam(r, "projectID"),
		chi.URLParam(r, "jobID"),
	)
	if err != nil {
		if strings.Contains(err.Error(), "not found") {
			apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_found", err.Error(), nil)
			return
		}
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, summary)
}

func (h *Handler) ExportMigrationJobAudit(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	format, verr := validateExportFormat(r.URL.Query().Get("format"))
	if verr != nil {
		apivalidate.WriteValidationError(w, r, verr)
		return
	}

	projectID := chi.URLParam(r, "projectID")
	jobID := chi.URLParam(r, "jobID")
	meta, err := h.svc.migration.PrepareJobAuditExport(r.Context(), projectID, jobID, format)
	if err != nil {
		if strings.Contains(err.Error(), "not found") {
			apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_found", err.Error(), nil)
			return
		}
		apivalidate.WriteInternal(w, r)
		return
	}

	w.Header().Set("Content-Type", meta.ContentType)
	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, meta.FileName))
	w.WriteHeader(http.StatusOK)
	if err := h.svc.migration.WriteJobAuditExport(
		r.Context(),
		projectID,
		jobID,
		r.URL.Query().Get("status"),
		format,
		w,
	); err != nil {
		h.logger.Error("export migration job audit", "error", err)
	}
}

func (h *Handler) ExportMigrationProjectAudit(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	format, verr := validateExportFormat(r.URL.Query().Get("format"))
	if verr != nil {
		apivalidate.WriteValidationError(w, r, verr)
		return
	}

	projectID := chi.URLParam(r, "projectID")
	meta, err := h.svc.migration.PrepareProjectAuditExport(r.Context(), projectID, format)
	if err != nil {
		if strings.Contains(err.Error(), "not found") {
			apiresponse.WriteError(w, r, http.StatusNotFound, "migration_project_not_found", err.Error(), nil)
			return
		}
		apivalidate.WriteInternal(w, r)
		return
	}

	w.Header().Set("Content-Type", meta.ContentType)
	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, meta.FileName))
	w.WriteHeader(http.StatusOK)
	if err := h.svc.migration.WriteProjectAuditExport(
		r.Context(),
		projectID,
		r.URL.Query().Get("status"),
		format,
		w,
	); err != nil {
		h.logger.Error("export migration project audit", "error", err)
	}
}

type updateSharedDriveModeRequest struct {
	Mode string `json:"shared_drive_mode"`
}

func (h *Handler) UpdateMigrationSharedDriveMode(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	var req updateSharedDriveModeRequest
	if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
		return
	}
	row, err := h.svc.migration.UpdateSharedDriveMode(r.Context(), chi.URLParam(r, "projectID"), req.Mode)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}

func (h *Handler) ListMigrationSharedDrives(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	rows, err := h.svc.migration.ListSharedDrives(r.Context(), chi.URLParam(r, "projectID"), r.URL.Query().Get("status"))
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"shared_drives": rows})
}

func (h *Handler) ApproveMigrationSharedDrive(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	row, err := h.svc.migration.ApproveSharedDrive(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "driveID"))
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusNotFound, "shared_drive_not_found", err.Error(), nil)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}

func (h *Handler) RejectMigrationSharedDrive(w http.ResponseWriter, r *http.Request) {
	if h.svc.migration == nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	row, err := h.svc.migration.RejectSharedDrive(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "driveID"))
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusNotFound, "shared_drive_not_found", err.Error(), nil)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, row)
}
