package api

import (
	"encoding/csv"
	"fmt"
	"net/http"
	"strconv"

	"archivmail/internal/audit"
)

// tenantScope returns the tenant filter for reconciliation queries: a
// domain_admin (and any other tenant-scoped role) is restricted to its own
// tenant, while superadmin (sess.TenantID == nil) sees all tenants. This
// mirrors handleMailTimeseries and prevents cross-tenant leakage of source
// figures (PROJ-55/61 tenant-isolation discipline).
func (s *Server) reconTenantScope(r *http.Request) *int64 {
	sess := sessionFromCtx(r.Context())
	if sess.TenantID != nil {
		return tenantFromCtx(r.Context())
	}
	return nil
}

// handleReconciliation returns the last N days (default 7) of completeness
// figures per source, tenant-scoped.
// GET /api/admin/reconciliation?days=7
func (s *Server) handleReconciliation(w http.ResponseWriter, r *http.Request) {
	if s.reconStore == nil {
		writeError(w, http.StatusServiceUnavailable, "reconciliation not enabled")
		return
	}

	days := 7
	if v := r.URL.Query().Get("days"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 {
			days = n
			if days > 90 {
				days = 90
			}
		}
	}

	tid := s.reconTenantScope(r)
	sources, err := s.reconStore.DashboardData(r.Context(), tid, days, s.reconThresholdPct)
	if err != nil {
		s.logger.Error("reconciliation dashboard query failed", "err", err)
		writeError(w, http.StatusInternalServerError, "reconciliation query failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{
		"days":          days,
		"threshold_pct": s.reconThresholdPct,
		"sources":       sources,
	})
}

// handleReconciliationExport streams the reconciliation report as CSV,
// tenant-scoped (analog PROJ-11 audit export).
// GET /api/admin/reconciliation/export.csv?days=30
func (s *Server) handleReconciliationExport(w http.ResponseWriter, r *http.Request) {
	if s.reconStore == nil {
		writeError(w, http.StatusServiceUnavailable, "reconciliation not enabled")
		return
	}

	days := 30
	if v := r.URL.Query().Get("days"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 366 {
			days = n
		}
	}

	tid := s.reconTenantScope(r)
	rows, err := s.reconStore.ExportRows(r.Context(), tid, days)
	if err != nil {
		s.logger.Error("reconciliation export query failed", "err", err)
		writeError(w, http.StatusInternalServerError, "reconciliation query failed")
		return
	}

	sess := sessionFromCtx(r.Context())

	w.Header().Set("Content-Type", "text/csv; charset=utf-8")
	w.Header().Set("Content-Disposition", `attachment; filename="reconciliation.csv"`)
	w.WriteHeader(http.StatusOK)

	cw := csv.NewWriter(w)
	cw.Write([]string{"date", "tenant_id", "source", "expected_count", "archived_count", "delta"}) //nolint:errcheck
	for _, row := range rows {
		cw.Write([]string{ //nolint:errcheck
			row.Date.UTC().Format("2006-01-02"),
			nullableInt(row.TenantID),
			reconSourceKey(row.SourceType, row.SourceID),
			nullableInt(row.ExpectedCount),
			strconv.FormatInt(row.ArchivedCount, 10),
			nullableInt(row.Delta),
		})
	}
	cw.Flush()

	s.audlog.Log(audit.Entry{
		EventType: audit.EventExport,
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Detail:    fmt.Sprintf("reconciliation csv: %d days, %d rows", days, len(rows)),
		Success:   true,
	})
}

// nullableInt formats a *int64 for CSV, emitting an empty string for nil.
func nullableInt(v *int64) string {
	if v == nil {
		return ""
	}
	return strconv.FormatInt(*v, 10)
}

// reconSourceKey mirrors reconciliation.SourceKey without importing the package
// into the CSV hot path (kept local and tiny).
func reconSourceKey(sourceType string, sourceID *int64) string {
	if sourceID != nil && (sourceType == "imap" || sourceType == "pop3") {
		return fmt.Sprintf("%s:%d", sourceType, *sourceID)
	}
	return sourceType
}
