package api

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"archivmail/internal/audit"
	"archivmail/internal/index"
	"archivmail/internal/storage"
	"archivmail/internal/userstore"
)

// dsgvoMaxHits caps the number of mails evaluated per request, mirroring the
// eDiscovery export limit (PROJ-39). Beyond this the admin is asked to narrow
// the date range.
const dsgvoMaxHits = 10000

// canManageDSGVO reports whether the role may create/process DSGVO requests.
func canManageDSGVO(role string) bool {
	switch role {
	case userstore.RoleSuperAdmin, userstore.RoleDomainAdmin, userstore.RoleAdmin:
		return true
	}
	return false
}

// canViewDSGVO reports whether the role may read DSGVO requests (incl. auditors).
func canViewDSGVO(role string) bool {
	if canManageDSGVO(role) {
		return true
	}
	switch role {
	case userstore.RoleAuditor, userstore.RoleDomainAuditor:
		return true
	}
	return false
}

// handleCreateDSGVORequest creates a DSGVO erasure request, runs the address
// search in the admin's tenant scope, evaluates retain_until per mail and
// stores the documented result.
//
// POST /api/admin/dsgvo
// Body: {address, date_from?, date_to?}
func (s *Server) handleCreateDSGVORequest(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if !canManageDSGVO(sess.Role) {
		writeError(w, http.StatusForbidden, "insufficient permissions")
		return
	}
	tenantID := tenantFromCtx(r.Context())

	var req struct {
		Address  string `json:"address"`
		DateFrom string `json:"date_from"`
		DateTo   string `json:"date_to"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}
	address := strings.ToLower(strings.TrimSpace(req.Address))
	if address == "" || !strings.Contains(address, "@") {
		writeError(w, http.StatusBadRequest, "valid e-mail address required")
		return
	}

	// Build the address search across from/to/cc/bcc.
	searchReq := index.SearchRequest{
		AnyAddress: address,
		PageSize:   dsgvoMaxHits,
		Page:       1,
	}
	// Bug-6: reject invalid date formats instead of silently ignoring them.
	if req.DateFrom != "" {
		t, err := time.Parse(time.DateOnly, req.DateFrom)
		if err != nil {
			writeError(w, http.StatusBadRequest, "invalid date_from format (expected YYYY-MM-DD)")
			return
		}
		searchReq.DateFrom = &t
	}
	if req.DateTo != "" {
		t, err := time.Parse(time.DateOnly, req.DateTo)
		if err != nil {
			writeError(w, http.StatusBadRequest, "invalid date_to format (expected YYYY-MM-DD)")
			return
		}
		t = t.Add(24*time.Hour - time.Second)
		searchReq.DateTo = &t
	}

	searchIdx := s.idx
	if s.idxMgr != nil && tenantID != nil {
		searchIdx = s.idxMgr.ForTenant(tenantID)
	}

	result, err := searchIdx.Search(searchReq)
	if err != nil {
		// Bug-3: failed searches must also be audited.
		if s.audlog != nil {
			s.audlog.Log(audit.Entry{
				EventType: audit.EventDSGVORequest,
				Username:  sess.Username,
				TenantID:  sess.TenantID,
				IPAddress: s.remoteIP(r),
				Success:   false,
				Detail:    fmt.Sprintf("dsgvo: search failed address=%q error=%v", address, err),
			})
		}
		writeError(w, http.StatusInternalServerError, "search failed")
		return
	}

	// Persist the request first so the vorgang is documented even if later steps fail.
	dsReq, err := s.store.CreateDSGVORequest(r.Context(), tenantID, address, sess.Username)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not create request")
		return
	}

	ids := make([]string, 0, len(result.Hits))
	for _, h := range result.Hits {
		ids = append(ids, h.ID)
	}
	meta, err := s.store.GetDSGVOMailMeta(r.Context(), ids, tenantID)
	if err != nil {
		// Bug-2: do not leave an orphaned "open" request behind on failure.
		s.failDSGVORequest(r.Context(), dsReq.ID)
		writeError(w, http.StatusInternalServerError, "metadata lookup failed")
		return
	}

	now := time.Now()
	summary := storage.DSGVOResultSummary{
		Truncated: result.Total > dsgvoMaxHits,
	}
	for _, id := range ids {
		m, ok := meta[id]
		if !ok {
			continue
		}
		summary.TotalHits++
		am := storage.DSGVOAffectedMail{
			MailID:      id,
			Subject:     m.Subject,
			RetainUntil: m.RetainUntil,
		}
		if !m.ReceivedAt.IsZero() {
			d := m.ReceivedAt
			am.Date = &d
		}
		if m.RetainUntil != nil && now.Before(*m.RetainUntil) {
			summary.Rejected++
			am.Deletable = false
			am.Reason = fmt.Sprintf("Abgelehnt - gesetzliche Aufbewahrungspflicht bis %s",
				m.RetainUntil.Format("2006-01-02"))
		} else {
			summary.Deletable++
			am.Deletable = true
			am.Reason = "Loeschbar - keine Aufbewahrungspflicht"
		}
		summary.Mails = append(summary.Mails, am)
	}

	status := dsgvoStatus(summary)
	if err := s.store.UpdateDSGVOResult(r.Context(), dsReq.ID, status, &summary); err != nil {
		// Bug-2: avoid a stuck "open" request when the result cannot be stored.
		s.failDSGVORequest(r.Context(), dsReq.ID)
		writeError(w, http.StatusInternalServerError, "could not store result")
		return
	}
	dsReq.Status = status
	dsReq.ResultSummary = &summary

	if s.audlog != nil {
		s.audlog.Log(audit.Entry{
			EventType: audit.EventDSGVORequest,
			Username:  sess.Username,
			TenantID:  sess.TenantID,
			IPAddress: s.remoteIP(r),
			Success:   true,
			Detail: fmt.Sprintf("dsgvo: address=%q hits=%d rejected=%d deletable=%d truncated=%t",
				address, summary.TotalHits, summary.Rejected, summary.Deletable, summary.Truncated),
		})
	}

	writeJSON(w, http.StatusOK, dsReq)
}

// failDSGVORequest marks a request as failed so no orphaned "open" entry
// remains when evaluation aborts after creation (PROJ-50 Bug-2). Errors here
// are intentionally swallowed: the original failure is already being reported
// to the caller, and a best-effort cleanup must not mask it.
func (s *Server) failDSGVORequest(ctx context.Context, id int64) {
	_ = s.store.MarkDSGVORequestFailed(ctx, id)
}

// dsgvoStatus computes the request status from the result summary.
func dsgvoStatus(s storage.DSGVOResultSummary) string {
	if s.TotalHits == 0 {
		return storage.DSGVOStatusCompleted
	}
	if s.Rejected > 0 && s.Deletable > 0 {
		return storage.DSGVOStatusPartial
	}
	if s.Rejected > 0 {
		return storage.DSGVOStatusPartial
	}
	return storage.DSGVOStatusCompleted
}

// handleListDSGVORequests lists requests in the caller's tenant scope.
// GET /api/admin/dsgvo
func (s *Server) handleListDSGVORequests(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if !canViewDSGVO(sess.Role) {
		writeError(w, http.StatusForbidden, "insufficient permissions")
		return
	}
	tenantID := tenantFromCtx(r.Context())
	list, err := s.store.ListDSGVORequests(r.Context(), tenantID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "list failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"requests": list})
}

// handleGetDSGVORequest returns a single request detail.
// GET /api/admin/dsgvo/{id}
func (s *Server) handleGetDSGVORequest(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if !canViewDSGVO(sess.Role) {
		writeError(w, http.StatusForbidden, "insufficient permissions")
		return
	}
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}
	tenantID := tenantFromCtx(r.Context())
	req, err := s.store.GetDSGVORequest(r.Context(), id, tenantID)
	if err != nil {
		writeError(w, http.StatusNotFound, "not found")
		return
	}
	writeJSON(w, http.StatusOK, req)
}

// handleDeleteDSGVOMails deletes the deletable mails of a request using the
// existing retention-aware delete mechanism (PROJ-34). Mails under retention
// lock are skipped. Each deletion is documented and the request summary is
// updated to reflect what was actually removed.
//
// POST /api/admin/dsgvo/{id}/delete
func (s *Server) handleDeleteDSGVOMails(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if !canManageDSGVO(sess.Role) {
		writeError(w, http.StatusForbidden, "insufficient permissions")
		return
	}
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}
	tenantID := tenantFromCtx(r.Context())
	req, err := s.store.GetDSGVORequest(r.Context(), id, tenantID)
	if err != nil || req.ResultSummary == nil {
		writeError(w, http.StatusNotFound, "not found")
		return
	}

	searchIdx := s.idx
	if s.idxMgr != nil && tenantID != nil {
		searchIdx = s.idxMgr.ForTenant(tenantID)
	}

	deleted := 0
	for i := range req.ResultSummary.Mails {
		m := &req.ResultSummary.Mails[i]
		if !m.Deletable || m.Deleted {
			continue
		}
		if err := s.store.Delete(m.MailID); err != nil {
			// Retention lock or missing file → leave as-is, keep documented.
			m.Reason = "Loeschung nicht moeglich: " + err.Error()
			m.Deletable = false
			continue
		}
		_ = searchIdx.Delete(m.MailID)
		m.Deleted = true
		m.Deletable = false
		m.Reason = "Geloescht auf DSGVO-Antrag"
		deleted++

		if s.audlog != nil {
			s.audlog.Log(audit.Entry{
				EventType: audit.EventDSGVORequest,
				Username:  sess.Username,
				TenantID:  sess.TenantID,
				IPAddress: s.remoteIP(r),
				MailID:    m.MailID,
				Success:   true,
				Detail:    fmt.Sprintf("dsgvo: deleted mail (request_id=%d address=%q)", id, req.RequestedAddress),
			})
		}
	}

	// Recompute counters.
	req.ResultSummary.Deleted += deleted
	req.ResultSummary.Deletable = 0
	for _, m := range req.ResultSummary.Mails {
		if m.Deletable {
			req.ResultSummary.Deletable++
		}
	}
	status := dsgvoStatus(*req.ResultSummary)
	if req.ResultSummary.Deleted > 0 && req.ResultSummary.Deletable == 0 && req.ResultSummary.Rejected == 0 {
		status = storage.DSGVOStatusCompleted
	}
	if err := s.store.UpdateDSGVOResult(r.Context(), id, status, req.ResultSummary); err != nil {
		writeError(w, http.StatusInternalServerError, "could not update request")
		return
	}
	req.Status = status

	writeJSON(w, http.StatusOK, map[string]interface{}{"deleted": deleted, "request": req})
}

// handleExportDSGVORequest produces a PDF summary suitable as a written reply
// to the data subject, documenting the legal basis for any rejection.
//
// GET /api/admin/dsgvo/{id}/export
func (s *Server) handleExportDSGVORequest(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if !canViewDSGVO(sess.Role) {
		writeError(w, http.StatusForbidden, "insufficient permissions")
		return
	}
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}
	tenantID := tenantFromCtx(r.Context())
	req, err := s.store.GetDSGVORequest(r.Context(), id, tenantID)
	if err != nil {
		writeError(w, http.StatusNotFound, "not found")
		return
	}

	pdf := buildDSGVOPDF(req)
	filename := fmt.Sprintf("dsgvo-antrag-%d.pdf", req.ID)
	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
	_, _ = w.Write(pdf)

	if s.audlog != nil {
		s.audlog.Log(audit.Entry{
			EventType: audit.EventExport,
			Username:  sess.Username,
			TenantID:  sess.TenantID,
			IPAddress: s.remoteIP(r),
			Success:   true,
			Detail:    fmt.Sprintf("dsgvo: export request_id=%d", req.ID),
		})
	}
}

// buildDSGVOPDF renders a DSGVO request summary as a PDF using the existing
// minimal PDF writer (PROJ-12 buildMailPDF pattern).
func buildDSGVOPDF(req *storage.DSGVORequest) []byte {
	p := newPDFWriter()
	pageW := 595.28
	pageH := 841.89
	margin := 50.0
	lineH := 14.0
	smallLineH := 12.0

	var cs bytes.Buffer
	y := pageH - margin
	newline := func(h float64) { y -= h }
	text := func(x, ypos, size float64, bold bool, s string) {
		font := "Helvetica"
		if bold {
			font = "Helvetica-Bold"
		}
		cs.WriteString(fmt.Sprintf("BT /%s %.1f Tf %.1f %.1f Td (%s) Tj ET\n",
			font, size, x, ypos, escapePDF(toLatinSafe(s))))
	}

	text(margin, y, 16, true, "DSGVO-Loeschersuchen - Bearbeitungsnachweis")
	newline(lineH * 2)

	text(margin, y, 10, true, "Antrags-ID:")
	text(margin+120, y, 10, false, strconv.FormatInt(req.ID, 10))
	newline(smallLineH)
	text(margin, y, 10, true, "Betroffene Adresse:")
	text(margin+120, y, 10, false, req.RequestedAddress)
	newline(smallLineH)
	text(margin, y, 10, true, "Bearbeitet von:")
	text(margin+120, y, 10, false, req.RequestedBy)
	newline(smallLineH)
	text(margin, y, 10, true, "Erstellt am:")
	text(margin+120, y, 10, false, req.CreatedAt.Format("02.01.2006 15:04:05"))
	newline(smallLineH)
	text(margin, y, 10, true, "Status:")
	text(margin+120, y, 10, false, req.Status)
	newline(lineH * 1.5)

	if req.ResultSummary != nil {
		sum := req.ResultSummary
		text(margin, y, 12, true, "Zusammenfassung")
		newline(lineH)
		text(margin, y, 10, false, fmt.Sprintf("Treffer gesamt: %d", sum.TotalHits))
		newline(smallLineH)
		text(margin, y, 10, false, fmt.Sprintf("Abgelehnt (Aufbewahrungspflicht): %d", sum.Rejected))
		newline(smallLineH)
		text(margin, y, 10, false, fmt.Sprintf("Loeschbar: %d", sum.Deletable))
		newline(smallLineH)
		text(margin, y, 10, false, fmt.Sprintf("Bereits geloescht: %d", sum.Deleted))
		newline(smallLineH)
		if sum.Truncated {
			text(margin, y, 9, false, fmt.Sprintf("Hinweis: Ergebnis auf %d Treffer begrenzt - Zeitraum eingrenzen.", dsgvoMaxHits))
			newline(smallLineH)
		}
		newline(6)

		text(margin, y, 12, true, "Begruendung der Rechtslage")
		newline(lineH)
		legal := "Soweit E-Mails einer gesetzlichen Aufbewahrungspflicht (GoBD/HGB/AO) " +
			"unterliegen, ueberwiegt diese den Loeschanspruch nach Art. 17 DSGVO. " +
			"Eine Loeschung erfolgt erst nach Ablauf der jeweiligen Aufbewahrungsfrist."
		for _, l := range wrapText(legal, 90) {
			text(margin, y, 9, false, l)
			newline(smallLineH)
		}
		newline(6)

		text(margin, y, 12, true, "Betroffene E-Mails")
		newline(lineH)
		for _, m := range sum.Mails {
			if y < margin+30 {
				text(margin, y, 8, false, "[... gekuerzt ...]")
				break
			}
			dateStr := ""
			if m.Date != nil {
				dateStr = m.Date.Format("2006-01-02")
			}
			subj := m.Subject
			if len(subj) > 60 {
				subj = subj[:60]
			}
			text(margin, y, 9, false, fmt.Sprintf("- [%s] %s", dateStr, subj))
			newline(smallLineH)
			text(margin+12, y, 8, false, m.Reason)
			newline(smallLineH)
		}
	}

	contentStream := cs.String()

	p.write("%PDF-1.4\n")
	p.startObj(1)
	p.write("<< /Type /Catalog /Pages 2 0 R >>\n")
	p.endObj()
	p.startObj(2)
	p.writef("<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 %.2f %.2f] >>\n", pageW, pageH)
	p.endObj()
	p.startObj(3)
	p.writef("<< /Type /Page /Parent 2 0 R\n")
	p.writef("   /Resources << /Font << /Helvetica << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\n")
	p.writef("                            /Helvetica-Bold << /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >> >> >>\n")
	p.writef("   /Contents 4 0 R\n")
	p.writef("   /MediaBox [0 0 %.2f %.2f]\n", pageW, pageH)
	p.write(">>\n")
	p.endObj()
	p.startObj(4)
	streamBytes := []byte("0.5 w\n" + contentStream)
	p.writef("<< /Length %d >>\n", len(streamBytes))
	p.write("stream\n")
	p.buf.Write(streamBytes)
	p.write("\nendstream\n")
	p.endObj()

	xrefOffset := p.buf.Len()
	p.writef("xref\n0 5\n")
	p.writef("0000000000 65535 f \n")
	for i := 0; i < 4; i++ {
		off := 0
		if i < len(p.offsets) {
			off = p.offsets[i]
		}
		p.writef("%010d 00000 n \n", off)
	}
	p.write("trailer\n")
	p.writef("<< /Size 5 /Root 1 0 R >>\n")
	p.write("startxref\n")
	p.writef("%d\n", xrefOffset)
	p.write("%%EOF\n")

	return p.buf.Bytes()
}
