package api

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"archivmail/internal/audit"
	"archivmail/internal/auth"
	"archivmail/internal/index"
	"archivmail/pkg/mailparser"
)

// apiKeyTenantPtr converts an API-key session tenant id (0 = no tenant) into a
// *int64 for audit logging (PROJ-55). Returns nil for the tenant-less case.
func apiKeyTenantPtr(id int64) *int64 {
	if id == 0 {
		return nil
	}
	return &id
}

// handleV1MethodNotAllowed returns 405 for non-GET methods on v1 endpoints.
func (s *Server) handleV1MethodNotAllowed(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", "GET")
	writeError(w, http.StatusMethodNotAllowed, "only GET is allowed")
}

// handleV1SearchMails handles GET /api/v1/mails — search/list mails for external CRM systems.
// Only GET is processed; all other methods return 405.
func (s *Server) handleV1SearchMails(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.Header().Set("Allow", "GET")
		writeError(w, http.StatusMethodNotAllowed, "only GET is allowed")
		return
	}

	akSess := auth.APIKeySessionFromCtx(r.Context())
	if akSess == nil {
		writeError(w, http.StatusUnauthorized, "missing API key session")
		return
	}

	// Parse query parameters.
	q := r.URL.Query().Get("q")
	fromFilter := r.URL.Query().Get("from")
	toFilter := r.URL.Query().Get("to")
	subjectFilter := r.URL.Query().Get("subject")
	dateFromStr := r.URL.Query().Get("date_from")
	dateToStr := r.URL.Query().Get("date_to")
	contactFilter := r.URL.Query().Get("contact")
	pageStr := r.URL.Query().Get("page")
	limitStr := r.URL.Query().Get("limit")

	page, _ := strconv.Atoi(pageStr)
	if page <= 0 {
		page = 1
	}
	limit, _ := strconv.Atoi(limitStr)
	if limit <= 0 {
		limit = 25
	}
	if limit > 100 {
		limit = 100
	}

	// Build search request.
	req := index.SearchRequest{
		Query:    q,
		PageSize: limit,
		Page:     page,
	}

	// User-role keys must always scope their search to a specific contact address.
	if akSess.Role == "user" && contactFilter == "" {
		writeError(w, http.StatusBadRequest, "user-role API keys require the 'contact' parameter")
		return
	}

	// "contact" searches both From and To fields via OwnEmail.
	if contactFilter != "" {
		req.OwnEmail = contactFilter
	} else {
		req.From = fromFilter
		req.To = toFilter
	}

	// Subject is appended to the general query.
	if subjectFilter != "" {
		if req.Query != "" {
			req.Query += " "
		}
		req.Query += "@subject " + subjectFilter
	}

	// Date range.
	if dateFromStr != "" {
		if t, err := time.Parse(time.RFC3339, dateFromStr); err == nil {
			req.DateFrom = &t
		} else if t, err := time.Parse(time.DateOnly, dateFromStr); err == nil {
			req.DateFrom = &t
		}
	}
	if dateToStr != "" {
		if t, err := time.Parse(time.RFC3339, dateToStr); err == nil {
			req.DateTo = &t
		} else if t, err := time.Parse(time.DateOnly, dateToStr); err == nil {
			t = t.Add(24*time.Hour - time.Second)
			req.DateTo = &t
		}
	}

	// Resolve per-tenant index.
	tenantID := akSess.TenantID
	searchIdx := s.idx
	usedTenantIndex := false
	if s.idxMgr != nil && tenantID != 0 {
		searchIdx = s.idxMgr.ForTenant(&tenantID)
		usedTenantIndex = true
	}

	result, err := searchIdx.Search(req)
	if err != nil {
		s.logger.Error("v1 search failed", "err", err, "api_key", akSess.KeyName)
		writeError(w, http.StatusInternalServerError, "search failed")
		return
	}

	// PROJ-64 (Security-Nachtrag): fail-closed tenant post-filter for the legacy
	// path where idxMgr is nil, mirroring the fallback in search_handlers.go.
	if tenantID != 0 && !usedTenantIndex && len(result.Hits) > 0 {
		allowedIDs, idErr := s.store.GetAllIDsByTenant(r.Context(), &tenantID)
		if idErr == nil {
			allowed := make(map[string]struct{}, len(allowedIDs))
			for _, id := range allowedIDs {
				allowed[id] = struct{}{}
			}
			filtered := result.Hits[:0]
			for _, h := range result.Hits {
				if _, ok := allowed[h.ID]; ok {
					filtered = append(filtered, h)
				}
			}
			result.Hits = filtered
			result.Total = len(filtered)
		}
	}

	// Audit log.
	s.audlog.Log(audit.Entry{
		EventType: audit.EventSearch,
		Username:  fmt.Sprintf("apikey:%s", akSess.KeyName),
		TenantID:  apiKeyTenantPtr(akSess.TenantID),
		Query:     q,
		Detail:    fmt.Sprintf("v1_api contact=%s from=%s to=%s", contactFilter, fromFilter, toFilter),
		Success:   true,
	})

	// Enrich hits with metadata.
	type v1Mail struct {
		ID             string `json:"id"`
		From           string `json:"from,omitempty"`
		To             string `json:"to,omitempty"`
		Subject        string `json:"subject,omitempty"`
		Date           string `json:"date,omitempty"`
		Size           int64  `json:"size,omitempty"`
		HasAttachments bool   `json:"has_attachments"`
	}

	mails := make([]v1Mail, 0, len(result.Hits))
	for _, h := range result.Hits {
		m := v1Mail{ID: h.ID}
		raw, loadErr := s.store.Load(h.ID)
		if loadErr != nil {
			continue
		}
		m.Size = int64(len(raw))
		pm, parseErr := mailparser.Parse(raw)
		if parseErr != nil {
			continue
		}
		m.From = pm.From
		if len(pm.To) > 0 {
			m.To = strings.Join(pm.To, ", ")
		}
		m.Subject = pm.Subject
		if !pm.Date.IsZero() {
			m.Date = pm.Date.UTC().Format(time.RFC3339)
		}
		m.HasAttachments = len(pm.Attachments) > 0

		mails = append(mails, m)
	}

	totalPages := (result.Total + limit - 1) / limit

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"mails": mails,
		"total": result.Total,
		"page":  page,
		"pages": totalPages,
	})
}

// handleV1GetMail handles GET /api/v1/mails/{message_id} — single mail metadata.
func (s *Server) handleV1GetMail(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.Header().Set("Allow", "GET")
		writeError(w, http.StatusMethodNotAllowed, "only GET is allowed")
		return
	}

	akSess := auth.APIKeySessionFromCtx(r.Context())
	if akSess == nil {
		writeError(w, http.StatusUnauthorized, "missing API key session")
		return
	}

	id := r.PathValue("message_id")
	if !isValidMailID(id) {
		writeError(w, http.StatusBadRequest, "invalid mail id")
		return
	}

	// Tenant isolation: verify mail belongs to this API key's tenant.
	if akSess.TenantID != 0 {
		mailTenant, _ := s.store.GetTenantForMail(r.Context(), id)
		if mailTenant == nil || *mailTenant != akSess.TenantID {
			writeError(w, http.StatusNotFound, "mail not found")
			return
		}
	}

	raw, err := s.store.Load(id)
	if err != nil {
		writeError(w, http.StatusNotFound, "mail not found")
		return
	}

	pm, err := mailparser.Parse(raw)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to parse mail")
		return
	}

	// Audit log.
	s.audlog.Log(audit.Entry{
		EventType: audit.EventMailView,
		Username:  fmt.Sprintf("apikey:%s", akSess.KeyName),
		TenantID:  apiKeyTenantPtr(akSess.TenantID),
		MailID:    id,
		Detail:    "v1_api",
		Success:   true,
	})

	type attachMeta struct {
		Index       int    `json:"index"`
		Filename    string `json:"filename"`
		ContentType string `json:"content_type"`
		Size        int    `json:"size"`
	}
	attachments := make([]attachMeta, len(pm.Attachments))
	for i, a := range pm.Attachments {
		attachments[i] = attachMeta{
			Index:       i,
			Filename:    a.Filename,
			ContentType: a.ContentType,
			Size:        a.Size,
		}
	}

	var dateStr string
	if !pm.Date.IsZero() {
		dateStr = pm.Date.UTC().Format(time.RFC3339)
	}

	// PROJ-44: expose ocr_status to external API consumers as well so CRM
	// integrations can decide whether to ask for ocr-text downloads.
	ocrStatus, _, _ := s.store.GetOCRMeta(r.Context(), id)
	if ocrStatus == "" {
		ocrStatus = "pending"
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"id":          id,
		"from":        pm.From,
		"to":          strings.Join(pm.To, ", "),
		"cc":          strings.Join(pm.CC, ", "),
		"subject":     pm.Subject,
		"date":        dateStr,
		"size":        len(raw),
		"body_plain":  pm.TextBody,
		"attachments": attachments,
		"ocr_status":  ocrStatus,
	})
}

// handleV1GetMailRaw handles GET /api/v1/mails/{message_id}/raw — download original EML.
func (s *Server) handleV1GetMailRaw(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.Header().Set("Allow", "GET")
		writeError(w, http.StatusMethodNotAllowed, "only GET is allowed")
		return
	}

	akSess := auth.APIKeySessionFromCtx(r.Context())
	if akSess == nil {
		writeError(w, http.StatusUnauthorized, "missing API key session")
		return
	}

	id := r.PathValue("message_id")
	if !isValidMailID(id) {
		writeError(w, http.StatusBadRequest, "invalid mail id")
		return
	}

	// Tenant isolation.
	if akSess.TenantID != 0 {
		mailTenant, _ := s.store.GetTenantForMail(r.Context(), id)
		if mailTenant == nil || *mailTenant != akSess.TenantID {
			writeError(w, http.StatusNotFound, "mail not found")
			return
		}
	}

	raw, err := s.store.Load(id)
	if err != nil {
		writeError(w, http.StatusNotFound, "mail not found")
		return
	}

	// Audit log.
	s.audlog.Log(audit.Entry{
		EventType: audit.EventExport,
		Username:  fmt.Sprintf("apikey:%s", akSess.KeyName),
		TenantID:  apiKeyTenantPtr(akSess.TenantID),
		MailID:    id,
		Detail:    "v1_api raw download",
		Success:   true,
	})

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.eml"`, id[:16]))
	w.Header().Set("Content-Length", strconv.Itoa(len(raw)))
	w.WriteHeader(http.StatusOK)
	w.Write(raw)
}
