package handlers

// export_data.go — v0.34.0 CS0
//
// Data export endpoints: user data export (GDPR "download my data")
// and team data export (admin). Streams .switchboard zip archives
// directly to the HTTP response.

import (
	"fmt"
	"log/slog"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"

	"chat-switchboard/export"
	"chat-switchboard/models"
	"chat-switchboard/storage"
	"chat-switchboard/store"
)

// DataExportHandler serves data export endpoints.
type DataExportHandler struct {
	stores   store.Stores
	objStore storage.ObjectStore
}

// NewDataExportHandler creates a new handler for data export operations.
func NewDataExportHandler(s store.Stores, obj storage.ObjectStore) *DataExportHandler {
	return &DataExportHandler{stores: s, objStore: obj}
}

// ExportMyData streams the requesting user's data as a .switchboard zip.
// GET /api/v1/export/me
func (h *DataExportHandler) ExportMyData(c *gin.Context) {
	ctx := c.Request.Context()
	userID := c.GetString("user_id")

	// ── Fetch user ──
	user, err := h.stores.Users.GetByID(ctx, userID)
	if err != nil || user == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
		return
	}

	// ── Fetch all user-scoped entities ──
	channels, err := h.stores.Export.UserChannels(ctx, userID)
	if err != nil {
		slog.Error("export: fetch channels", "error", err, "user_id", userID)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export channels"})
		return
	}

	channelIDs := make([]string, len(channels))
	for i, ch := range channels {
		channelIDs[i] = ch.ID
	}

	messages, err := h.stores.Export.UserMessages(ctx, channelIDs)
	if err != nil {
		slog.Error("export: fetch messages", "error", err, "user_id", userID)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export messages"})
		return
	}

	participants, err := h.stores.Export.UserChannelParticipants(ctx, channelIDs)
	if err != nil {
		slog.Error("export: fetch participants", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export participants"})
		return
	}

	channelModels, err := h.stores.Export.UserChannelModels(ctx, channelIDs)
	if err != nil {
		slog.Error("export: fetch channel models", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export channel models"})
		return
	}

	cursors, err := h.stores.Export.UserChannelCursors(ctx, userID, channelIDs)
	if err != nil {
		slog.Error("export: fetch cursors", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export cursors"})
		return
	}

	notes, err := h.stores.Export.UserNotes(ctx, userID)
	if err != nil {
		slog.Error("export: fetch notes", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export notes"})
		return
	}

	noteIDs := make([]string, len(notes))
	for i, n := range notes {
		noteIDs[i] = n.ID
	}

	noteLinks, err := h.stores.Export.UserNoteLinks(ctx, noteIDs)
	if err != nil {
		slog.Error("export: fetch note links", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export note links"})
		return
	}

	memories, err := h.stores.Export.UserMemories(ctx, userID)
	if err != nil {
		slog.Error("export: fetch memories", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export memories"})
		return
	}

	projects, err := h.stores.Export.UserProjects(ctx, userID)
	if err != nil {
		slog.Error("export: fetch projects", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export projects"})
		return
	}

	projectIDs := make([]string, len(projects))
	for i, p := range projects {
		projectIDs[i] = p.ID
	}

	projectChannels, err := h.stores.Export.UserProjectChannels(ctx, projectIDs)
	if err != nil {
		slog.Error("export: fetch project channels", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export project channels"})
		return
	}

	projectKBs, err := h.stores.Export.UserProjectKBs(ctx, projectIDs)
	if err != nil {
		slog.Error("export: fetch project KBs", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export project KBs"})
		return
	}

	projectNotes, err := h.stores.Export.UserProjectNotes(ctx, projectIDs)
	if err != nil {
		slog.Error("export: fetch project notes", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export project notes"})
		return
	}

	workspaces, err := h.stores.Export.UserWorkspaces(ctx, userID)
	if err != nil {
		slog.Error("export: fetch workspaces", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export workspaces"})
		return
	}

	workspaceIDs := make([]string, len(workspaces))
	for i, w := range workspaces {
		workspaceIDs[i] = w.ID
	}

	workspaceFiles, err := h.stores.Export.UserWorkspaceFiles(ctx, workspaceIDs)
	if err != nil {
		slog.Error("export: fetch workspace files", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export workspace files"})
		return
	}

	files, err := h.stores.Export.UserFiles(ctx, userID)
	if err != nil {
		slog.Error("export: fetch files", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export files"})
		return
	}

	folders, err := h.stores.Export.UserFolders(ctx, userID)
	if err != nil {
		slog.Error("export: fetch folders", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export folders"})
		return
	}

	userModelSettings, err := h.stores.Export.UserModelSettings(ctx, userID)
	if err != nil {
		slog.Error("export: fetch user model settings", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export user settings"})
		return
	}

	notifPrefs, err := h.stores.Export.UserNotifPrefs(ctx, userID)
	if err != nil {
		slog.Error("export: fetch notification prefs", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export notification prefs"})
		return
	}

	usageEntries, err := h.stores.Export.UserUsageEntries(ctx, userID)
	if err != nil {
		slog.Error("export: fetch usage entries", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export usage entries"})
		return
	}

	personaGroups, err := h.stores.Export.UserPersonaGroups(ctx, userID)
	if err != nil {
		slog.Error("export: fetch persona groups", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export persona groups"})
		return
	}

	pgIDs := make([]string, len(personaGroups))
	for i, pg := range personaGroups {
		pgIDs[i] = pg.ID
	}

	personaGroupMembers, err := h.stores.Export.UserPersonaGroupMembers(ctx, pgIDs)
	if err != nil {
		slog.Error("export: fetch persona group members", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export persona group members"})
		return
	}

	// ── Build manifest ──
	sanitizedUser := export.SanitizeUser(user)
	sanitizedChannels := export.SanitizeChannels(channels)
	sanitizedMessages := export.SanitizeMessages(messages)
	sanitizedChannelModels := export.SanitizeChannelModels(channelModels)
	sanitizedUserModelSettings := export.SanitizeUserModelSettings(userModelSettings)
	sanitizedUsageEntries := export.SanitizeUsageEntries(usageEntries)
	sanitizedWorkspaces := export.SanitizeWorkspaces(workspaces)

	counts := map[string]int{
		"users":                    1,
		"channels":                 len(channels),
		"messages":                 len(messages),
		"channel_participants":     len(participants),
		"channel_models":           len(channelModels),
		"channel_cursors":          len(cursors),
		"notes":                    len(notes),
		"note_links":               len(noteLinks),
		"memories":                 len(memories),
		"projects":                 len(projects),
		"project_channels":         len(projectChannels),
		"project_knowledge_bases":  len(projectKBs),
		"project_notes":            len(projectNotes),
		"workspaces":               len(workspaces),
		"workspace_files":          len(workspaceFiles),
		"files":                    len(files),
		"folders":                  len(folders),
		"user_settings":            len(userModelSettings),
		"notification_preferences": len(notifPrefs),
		"usage_entries":            len(usageEntries),
		"persona_groups":           len(personaGroups),
		"persona_group_members":    len(personaGroupMembers),
	}

	manifest := &export.Manifest{
		Version:       "0.34.0",
		FormatVersion: export.FormatVersion,
		ExportType:    "user",
		CreatedAt:     time.Now().UTC(),
		ExportedBy:    userID,
		EntityCounts:  counts,
		Scope:         &export.ManifestScope{UserID: userID},
	}

	// ── Stream zip to response ──
	filename := fmt.Sprintf("switchboard-export-%s%s", userID[:8], export.ExportExtension)
	c.Header("Content-Type", export.ExportContentType)
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))

	aw := export.NewArchiveWriter(c.Writer)
	defer aw.Close()

	if err := aw.WriteManifest(manifest); err != nil {
		slog.Error("export: write manifest", "error", err)
		return
	}

	// Write entity JSON files
	writeEntity := func(name string, data interface{}) {
		if _, err := aw.WriteEntityJSON(name, data); err != nil {
			slog.Error("export: write entity", "name", name, "error", err)
		}
	}

	writeEntity("users", []interface{}{sanitizedUser})
	writeEntity("channels", sanitizedChannels)
	writeEntity("messages", sanitizedMessages)
	writeEntity("channel_participants", participants)
	writeEntity("channel_models", sanitizedChannelModels)
	writeEntity("channel_cursors", cursors)
	writeEntity("notes", notes)
	writeEntity("note_links", noteLinks)
	writeEntity("memories", memories)
	writeEntity("projects", projects)
	writeEntity("project_channels", projectChannels)
	writeEntity("project_knowledge_bases", projectKBs)
	writeEntity("project_notes", projectNotes)
	writeEntity("workspaces", sanitizedWorkspaces)
	writeEntity("workspace_files", workspaceFiles)
	writeEntity("folders", folders)
	writeEntity("user_settings", sanitizedUserModelSettings)
	writeEntity("notification_preferences", notifPrefs)
	writeEntity("usage_entries", sanitizedUsageEntries)
	writeEntity("persona_groups", personaGroups)
	writeEntity("persona_group_members", personaGroupMembers)

	// Write file metadata (without storage_key — already excluded by json:"-" tag)
	writeEntity("files", files)

	// ── Stream file blobs ──
	var warnings []string
	if h.objStore != nil {
		fileCount := 0
		for _, f := range files {
			if fileCount >= export.MaxExportFiles {
				warnings = append(warnings, "file blob limit reached, some files skipped")
				break
			}
			if f.SizeBytes > export.MaxExportFileSize {
				warnings = append(warnings, fmt.Sprintf("file %s too large (%d bytes), skipped", f.Filename, f.SizeBytes))
				continue
			}
			rc, _, _, err := h.objStore.Get(ctx, f.StorageKey)
			if err != nil {
				warnings = append(warnings, fmt.Sprintf("file %s not found in storage, skipped", f.Filename))
				continue
			}
			zipPath := f.ID + "/" + f.Filename
			if _, err := aw.WriteFile(zipPath, rc); err != nil {
				rc.Close()
				warnings = append(warnings, fmt.Sprintf("file %s write error: %v, skipped", f.Filename, err))
				continue
			}
			rc.Close()
			fileCount++
		}
	}

	if len(warnings) > 0 {
		writeEntity("export_warnings", warnings)
	}

	// Audit log
	h.stores.Audit.Log(ctx, &models.AuditEntry{
		ActorID:      &userID,
		Action:       "user.export",
		ResourceType: "user",
		ResourceID:   userID,
		Metadata:     models.JSONMap{"entity_counts": counts},
	})
}

// ExportTeam streams a team's data as a .switchboard zip.
// GET /api/v1/admin/teams/:id/export
func (h *DataExportHandler) ExportTeam(c *gin.Context) {
	ctx := c.Request.Context()
	userID := c.GetString("user_id")
	teamID := c.Param("id")

	// Fetch team to verify it exists
	team, err := h.stores.Teams.GetByID(ctx, teamID)
	if err != nil || team == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
		return
	}

	// Fetch all team-scoped entities
	channels, err := h.stores.Export.TeamChannels(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch team channels", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export team channels"})
		return
	}

	channelIDs := make([]string, len(channels))
	for i, ch := range channels {
		channelIDs[i] = ch.ID
	}

	messages, err := h.stores.Export.UserMessages(ctx, channelIDs)
	if err != nil {
		slog.Error("export: fetch team messages", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export team messages"})
		return
	}

	members, err := h.stores.Export.TeamMembers(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch team members", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export team members"})
		return
	}

	personas, err := h.stores.Export.TeamPersonas(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch team personas", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export team personas"})
		return
	}

	personaIDs := make([]string, len(personas))
	for i, p := range personas {
		personaIDs[i] = p.ID
	}

	personaKBs, err := h.stores.Export.TeamPersonaKBs(ctx, personaIDs)
	if err != nil {
		slog.Error("export: fetch persona KBs", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export persona KBs"})
		return
	}

	kbs, err := h.stores.Export.TeamKnowledgeBases(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch team KBs", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export team KBs"})
		return
	}

	kbIDs := make([]string, len(kbs))
	for i, kb := range kbs {
		kbIDs[i] = kb.ID
	}

	kbDocs, err := h.stores.Export.TeamKBDocuments(ctx, kbIDs)
	if err != nil {
		slog.Error("export: fetch KB docs", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export KB documents"})
		return
	}

	workflows, err := h.stores.Export.TeamWorkflows(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch workflows", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export workflows"})
		return
	}

	workflowIDs := make([]string, len(workflows))
	for i, w := range workflows {
		workflowIDs[i] = w.ID
	}

	workflowVersions, err := h.stores.Export.TeamWorkflowVersions(ctx, workflowIDs)
	if err != nil {
		slog.Error("export: fetch workflow versions", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export workflow versions"})
		return
	}

	workflowStages, err := h.stores.Export.TeamWorkflowStages(ctx, workflowIDs)
	if err != nil {
		slog.Error("export: fetch workflow stages", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export workflow stages"})
		return
	}

	groups, err := h.stores.Export.TeamGroups(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch groups", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export groups"})
		return
	}

	groupIDs := make([]string, len(groups))
	for i, g := range groups {
		groupIDs[i] = g.ID
	}

	groupMembers, err := h.stores.Export.TeamGroupMembers(ctx, groupIDs)
	if err != nil {
		slog.Error("export: fetch group members", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export group members"})
		return
	}

	resourceGrants, err := h.stores.Export.TeamResourceGrants(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch resource grants", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export resource grants"})
		return
	}

	projects, err := h.stores.Export.TeamProjects(ctx, teamID)
	if err != nil {
		slog.Error("export: fetch team projects", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export team projects"})
		return
	}

	// Sanitize
	sanitizedChannels := export.SanitizeChannels(channels)
	sanitizedMessages := export.SanitizeMessages(messages)
	sanitizedPersonas := export.SanitizePersonas(personas)
	sanitizedWorkflows := export.SanitizeWorkflows(workflows)

	counts := map[string]int{
		"team":                 1,
		"channels":             len(channels),
		"messages":             len(messages),
		"members":              len(members),
		"personas":             len(personas),
		"persona_kbs":          len(personaKBs),
		"knowledge_bases":      len(kbs),
		"kb_documents":         len(kbDocs),
		"workflows":            len(workflows),
		"workflow_versions":    len(workflowVersions),
		"workflow_stages":      len(workflowStages),
		"groups":               len(groups),
		"group_members":        len(groupMembers),
		"resource_grants":      len(resourceGrants),
		"projects":             len(projects),
	}

	manifest := &export.Manifest{
		Version:       "0.34.0",
		FormatVersion: export.FormatVersion,
		ExportType:    "team",
		CreatedAt:     time.Now().UTC(),
		ExportedBy:    userID,
		EntityCounts:  counts,
		Scope:         &export.ManifestScope{TeamID: teamID},
	}

	filename := fmt.Sprintf("switchboard-team-%s%s", teamID[:8], export.ExportExtension)
	c.Header("Content-Type", export.ExportContentType)
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))

	aw := export.NewArchiveWriter(c.Writer)
	defer aw.Close()

	if err := aw.WriteManifest(manifest); err != nil {
		slog.Error("export: write manifest", "error", err)
		return
	}

	writeEntity := func(name string, data interface{}) {
		if _, err := aw.WriteEntityJSON(name, data); err != nil {
			slog.Error("export: write entity", "name", name, "error", err)
		}
	}

	writeEntity("team", []interface{}{team})
	writeEntity("channels", sanitizedChannels)
	writeEntity("messages", sanitizedMessages)
	writeEntity("members", members)
	writeEntity("personas", sanitizedPersonas)
	writeEntity("persona_knowledge_bases", personaKBs)
	writeEntity("knowledge_bases", kbs)
	writeEntity("kb_documents", kbDocs)
	writeEntity("workflows", sanitizedWorkflows)
	writeEntity("workflow_versions", workflowVersions)
	writeEntity("workflow_stages", workflowStages)
	writeEntity("groups", groups)
	writeEntity("group_members", groupMembers)
	writeEntity("resource_grants", resourceGrants)
	writeEntity("projects", projects)

	h.stores.Audit.Log(ctx, &models.AuditEntry{
		ActorID:      &userID,
		Action:       "team.export",
		ResourceType: "team",
		ResourceID:   teamID,
		Metadata:     models.JSONMap{"entity_counts": counts},
	})
}
