package audit

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/google/uuid"
)

// ExportFormat represents the export format
type ExportFormat string

const (
	FormatCSV  ExportFormat = "csv"
	FormatJSON ExportFormat = "json"
)

// Exporter exports audit data in various formats
type Exporter struct {
	store *Store
}

// NewExporter creates a new exporter
func NewExporter(store *Store) *Exporter {
	return &Exporter{store: store}
}

// ExportOptions defines export options
type ExportOptions struct {
	TenantID    uuid.UUID
	NamespaceID *uuid.UUID
	UserID      *uuid.UUID
	StartDate   time.Time
	EndDate     time.Time
	Format      ExportFormat
	IncludePII  bool // If false, redact PII fields
}

// ExportLLMAudit exports LLM audit entries
func (e *Exporter) ExportLLMAudit(ctx context.Context, w io.Writer, opts *ExportOptions) error {
	filter := &LLMAuditFilter{
		TenantID:    opts.TenantID,
		NamespaceID: opts.NamespaceID,
		UserID:      opts.UserID,
		StartDate:   &opts.StartDate,
		EndDate:     &opts.EndDate,
		Limit:       0, // No limit for export
	}

	entries, _, err := e.store.QueryLLMAuditEntries(ctx, filter)
	if err != nil {
		return fmt.Errorf("failed to query audit entries: %w", err)
	}

	switch opts.Format {
	case FormatCSV:
		return e.exportLLMAuditCSV(w, entries, opts.IncludePII)
	case FormatJSON:
		return e.exportLLMAuditJSON(w, entries, opts.IncludePII)
	default:
		return fmt.Errorf("unsupported format: %s", opts.Format)
	}
}

// ExportGeneralAudit exports general audit entries
func (e *Exporter) ExportGeneralAudit(ctx context.Context, w io.Writer, opts *ExportOptions) error {
	filter := &GeneralAuditFilter{
		TenantID:    opts.TenantID,
		NamespaceID: opts.NamespaceID,
		UserID:      opts.UserID,
		StartDate:   &opts.StartDate,
		EndDate:     &opts.EndDate,
		Limit:       0,
	}

	entries, _, err := e.store.QueryGeneralAuditEntries(ctx, filter)
	if err != nil {
		return fmt.Errorf("failed to query audit entries: %w", err)
	}

	switch opts.Format {
	case FormatCSV:
		return e.exportGeneralAuditCSV(w, entries, opts.IncludePII)
	case FormatJSON:
		return e.exportGeneralAuditJSON(w, entries, opts.IncludePII)
	default:
		return fmt.Errorf("unsupported format: %s", opts.Format)
	}
}

func (e *Exporter) exportLLMAuditCSV(w io.Writer, entries []*LLMAuditEntry, includePII bool) error {
	writer := csv.NewWriter(w)
	defer writer.Flush()

	// Write header
	header := []string{
		"ID", "Tenant ID", "Namespace ID", "User ID", "Session ID",
		"Operation", "Model", "Provider", "Prompt Hash", "Prompt Length", "Response Length",
		"Tokens Used", "Duration (ms)", "PII Detected", "PII Types", "PII Redacted",
		"Policy ID", "Policy Violations", "Data Categories", "Error", "Created At",
	}
	if err := writer.Write(header); err != nil {
		return err
	}

	// Write entries
	for _, entry := range entries {
		namespaceID := ""
		if entry.NamespaceID != nil {
			namespaceID = entry.NamespaceID.String()
		}

		policyID := ""
		if entry.PolicyID != nil {
			policyID = entry.PolicyID.String()
		}

		userID := entry.UserID.String()
		sessionID := entry.SessionID
		if !includePII {
			userID = "[REDACTED]"
			sessionID = "[REDACTED]"
		}

		row := []string{
			entry.ID.String(),
			entry.TenantID.String(),
			namespaceID,
			userID,
			sessionID,
			entry.Operation,
			entry.ModelUsed,
			entry.Provider,
			entry.PromptHash,
			fmt.Sprintf("%d", entry.PromptLength),
			fmt.Sprintf("%d", entry.ResponseLength),
			fmt.Sprintf("%d", entry.TokensUsed),
			fmt.Sprintf("%d", entry.DurationMS),
			fmt.Sprintf("%t", entry.PIIDetected),
			strings.Join(entry.PIITypesDetected, ";"),
			fmt.Sprintf("%t", entry.PIIRedacted),
			policyID,
			strings.Join(entry.PolicyViolations, ";"),
			strings.Join(entry.DataCategoriesAccessed, ";"),
			entry.ErrorMessage,
			entry.CreatedAt.Format(time.RFC3339),
		}

		if err := writer.Write(row); err != nil {
			return err
		}
	}

	return nil
}

func (e *Exporter) exportLLMAuditJSON(w io.Writer, entries []*LLMAuditEntry, includePII bool) error {
	// Redact PII if needed
	if !includePII {
		for _, entry := range entries {
			entry.UserID = uuid.Nil
			entry.SessionID = "[REDACTED]"
			entry.RequestMetadata = nil
		}
	}

	encoder := json.NewEncoder(w)
	encoder.SetIndent("", "  ")
	return encoder.Encode(map[string]any{
		"type":       "llm_audit_export",
		"exported_at": time.Now().UTC().Format(time.RFC3339),
		"count":      len(entries),
		"entries":    entries,
	})
}

func (e *Exporter) exportGeneralAuditCSV(w io.Writer, entries []*GeneralAuditEntry, includePII bool) error {
	writer := csv.NewWriter(w)
	defer writer.Flush()

	// Write header
	header := []string{
		"ID", "Tenant ID", "Namespace ID", "User ID",
		"Action", "Resource Type", "Resource ID",
		"IP Address", "User Agent", "Reason", "Created At",
	}
	if err := writer.Write(header); err != nil {
		return err
	}

	// Write entries
	for _, entry := range entries {
		namespaceID := ""
		if entry.NamespaceID != nil {
			namespaceID = entry.NamespaceID.String()
		}

		resourceID := ""
		if entry.ResourceID != nil {
			resourceID = entry.ResourceID.String()
		}

		userID := entry.UserID.String()
		ipAddress := entry.IPAddress
		userAgent := entry.UserAgent
		if !includePII {
			userID = "[REDACTED]"
			ipAddress = "[REDACTED]"
			userAgent = "[REDACTED]"
		}

		row := []string{
			entry.ID.String(),
			entry.TenantID.String(),
			namespaceID,
			userID,
			entry.Action,
			entry.ResourceType,
			resourceID,
			ipAddress,
			userAgent,
			entry.Reason,
			entry.CreatedAt.Format(time.RFC3339),
		}

		if err := writer.Write(row); err != nil {
			return err
		}
	}

	return nil
}

func (e *Exporter) exportGeneralAuditJSON(w io.Writer, entries []*GeneralAuditEntry, includePII bool) error {
	// Redact PII if needed
	if !includePII {
		for _, entry := range entries {
			entry.UserID = uuid.Nil
			entry.IPAddress = "[REDACTED]"
			entry.UserAgent = "[REDACTED]"
		}
	}

	encoder := json.NewEncoder(w)
	encoder.SetIndent("", "  ")
	return encoder.Encode(map[string]any{
		"type":        "general_audit_export",
		"exported_at": time.Now().UTC().Format(time.RFC3339),
		"count":       len(entries),
		"entries":     entries,
	})
}

// ExportUsageStats exports LLM usage statistics
func (e *Exporter) ExportUsageStats(ctx context.Context, w io.Writer, tenantID uuid.UUID, startDate, endDate time.Time, format ExportFormat) error {
	stats, err := e.store.GetLLMUsageStats(ctx, tenantID, startDate, endDate)
	if err != nil {
		return fmt.Errorf("failed to get usage stats: %w", err)
	}

	report := map[string]any{
		"type":              "llm_usage_report",
		"tenant_id":         tenantID.String(),
		"period_start":      startDate.Format(time.RFC3339),
		"period_end":        endDate.Format(time.RFC3339),
		"generated_at":      time.Now().UTC().Format(time.RFC3339),
		"total_requests":    stats.TotalRequests,
		"total_tokens":      stats.TotalTokens,
		"total_duration_ms": stats.TotalDurationMS,
		"requests_with_pii": stats.RequestsWithPII,
		"policy_violations": stats.PolicyViolations,
		"models_used":       stats.ModelsUsed,
	}

	switch format {
	case FormatJSON:
		encoder := json.NewEncoder(w)
		encoder.SetIndent("", "  ")
		return encoder.Encode(report)
	case FormatCSV:
		writer := csv.NewWriter(w)
		defer writer.Flush()

		// Write summary as CSV
		if err := writer.Write([]string{"Metric", "Value"}); err != nil {
			return err
		}

		rows := [][]string{
			{"Total Requests", fmt.Sprintf("%d", stats.TotalRequests)},
			{"Total Tokens", fmt.Sprintf("%d", stats.TotalTokens)},
			{"Total Duration (ms)", fmt.Sprintf("%d", stats.TotalDurationMS)},
			{"Requests with PII", fmt.Sprintf("%d", stats.RequestsWithPII)},
			{"Policy Violations", fmt.Sprintf("%d", stats.PolicyViolations)},
		}

		for model, count := range stats.ModelsUsed {
			rows = append(rows, []string{fmt.Sprintf("Model: %s", model), fmt.Sprintf("%d", count)})
		}

		for _, row := range rows {
			if err := writer.Write(row); err != nil {
				return err
			}
		}

		return nil
	default:
		return fmt.Errorf("unsupported format: %s", format)
	}
}

// ComplianceReport generates a compliance report
type ComplianceReport struct {
	TenantID          uuid.UUID         `json:"tenant_id"`
	PeriodStart       time.Time         `json:"period_start"`
	PeriodEnd         time.Time         `json:"period_end"`
	GeneratedAt       time.Time         `json:"generated_at"`
	TotalLLMRequests  int               `json:"total_llm_requests"`
	TotalTokensUsed   int               `json:"total_tokens_used"`
	PIIIncidents      int               `json:"pii_incidents"`
	PIIRedactionRate  float64           `json:"pii_redaction_rate"`
	PolicyViolations  int               `json:"policy_violations"`
	ModelsUsed        map[string]int    `json:"models_used"`
	TopUsers          []UserActivity    `json:"top_users,omitempty"`
	NamespaceBreakdown []NamespaceStats `json:"namespace_breakdown,omitempty"`
}

// UserActivity represents user activity in the report
type UserActivity struct {
	UserID        uuid.UUID `json:"user_id"`
	RequestCount  int       `json:"request_count"`
	TokensUsed    int       `json:"tokens_used"`
	PIIIncidents  int       `json:"pii_incidents"`
}

// NamespaceStats represents namespace statistics
type NamespaceStats struct {
	NamespaceID   uuid.UUID `json:"namespace_id"`
	NamespaceName string    `json:"namespace_name,omitempty"`
	RequestCount  int       `json:"request_count"`
	TokensUsed    int       `json:"tokens_used"`
}

// GenerateComplianceReport generates a detailed compliance report
func (e *Exporter) GenerateComplianceReport(ctx context.Context, tenantID uuid.UUID, startDate, endDate time.Time) (*ComplianceReport, error) {
	// Get basic stats
	stats, err := e.store.GetLLMUsageStats(ctx, tenantID, startDate, endDate)
	if err != nil {
		return nil, err
	}

	// Calculate PII redaction rate
	var redactionRate float64
	if stats.RequestsWithPII > 0 {
		// Query entries with PII to check redaction
		filter := &LLMAuditFilter{
			TenantID:    tenantID,
			PIIDetected: boolPtr(true),
			StartDate:   &startDate,
			EndDate:     &endDate,
		}
		entries, _, err := e.store.QueryLLMAuditEntries(ctx, filter)
		if err == nil {
			redactedCount := 0
			for _, e := range entries {
				if e.PIIRedacted {
					redactedCount++
				}
			}
			redactionRate = float64(redactedCount) / float64(len(entries)) * 100
		}
	}

	report := &ComplianceReport{
		TenantID:         tenantID,
		PeriodStart:      startDate,
		PeriodEnd:        endDate,
		GeneratedAt:      time.Now().UTC(),
		TotalLLMRequests: stats.TotalRequests,
		TotalTokensUsed:  stats.TotalTokens,
		PIIIncidents:     stats.RequestsWithPII,
		PIIRedactionRate: redactionRate,
		PolicyViolations: stats.PolicyViolations,
		ModelsUsed:       stats.ModelsUsed,
	}

	return report, nil
}

// ExportComplianceReport exports a compliance report
func (e *Exporter) ExportComplianceReport(ctx context.Context, w io.Writer, tenantID uuid.UUID, startDate, endDate time.Time, format ExportFormat) error {
	report, err := e.GenerateComplianceReport(ctx, tenantID, startDate, endDate)
	if err != nil {
		return err
	}

	switch format {
	case FormatJSON:
		encoder := json.NewEncoder(w)
		encoder.SetIndent("", "  ")
		return encoder.Encode(report)
	case FormatCSV:
		writer := csv.NewWriter(w)
		defer writer.Flush()

		// Write header
		if err := writer.Write([]string{"Metric", "Value"}); err != nil {
			return err
		}

		rows := [][]string{
			{"Report Type", "AI Compliance Report"},
			{"Tenant ID", report.TenantID.String()},
			{"Period Start", report.PeriodStart.Format(time.RFC3339)},
			{"Period End", report.PeriodEnd.Format(time.RFC3339)},
			{"Generated At", report.GeneratedAt.Format(time.RFC3339)},
			{"Total LLM Requests", fmt.Sprintf("%d", report.TotalLLMRequests)},
			{"Total Tokens Used", fmt.Sprintf("%d", report.TotalTokensUsed)},
			{"PII Incidents", fmt.Sprintf("%d", report.PIIIncidents)},
			{"PII Redaction Rate", fmt.Sprintf("%.2f%%", report.PIIRedactionRate)},
			{"Policy Violations", fmt.Sprintf("%d", report.PolicyViolations)},
		}

		for _, row := range rows {
			if err := writer.Write(row); err != nil {
				return err
			}
		}

		return nil
	default:
		return fmt.Errorf("unsupported format: %s", format)
	}
}

func boolPtr(b bool) *bool {
	return &b
}
