package userbilling

import (
	"context"
	"database/sql"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"
)

func (s *Server) handleAdminUsage(w http.ResponseWriter, r *http.Request) {
	if !s.internalAllowed(w, r) || !s.databaseAvailable(w) {
		return
	}
	query := parseAdminAuditQuery(r)
	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()
	rows, total, err := s.adminUsageRows(ctx, query)
	if err != nil {
		s.logDatabaseError("admin_usage", err)
		writeAPIError(w, http.StatusInternalServerError, "admin_usage_failed", "could not load usage records")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"contractVersion": "user-billing-admin-usage-v1", "rows": rows, "count": len(rows), "total": total, "pagination": pagination(query.Page, query.PageSize, total), "filters": auditFilterSummary(query), "valuesRedacted": true})
}

func (s *Server) handleAdminUsageExport(w http.ResponseWriter, r *http.Request) {
	if !s.internalAllowed(w, r) || !s.databaseAvailable(w) {
		return
	}
	query := parseAdminAuditQuery(r)
	query.Page = 1
	query.PageSize = queryLimit(r, "limit", 1000, 5000)
	ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()
	rows, _, err := s.adminUsageRows(ctx, query)
	if err != nil {
		s.logDatabaseError("admin_usage_export", err)
		writeAPIError(w, http.StatusInternalServerError, "admin_usage_export_failed", "could not export usage records")
		return
	}
	w.Header().Set("Content-Type", "text/csv; charset=utf-8")
	w.Header().Set("Content-Disposition", `attachment; filename="hwlab-admin-usage.csv"`)
	writer := csv.NewWriter(w)
	_ = writer.Write([]string{"id", "created_at", "user_id", "email", "username", "api_key_id", "service_id", "resource_type", "unit", "quantity", "credits", "status", "idempotency_key", "metadata_json"})
	for _, row := range rows {
		_ = writer.Write([]string{row.ID, row.CreatedAt.Format(time.RFC3339), row.UserID, row.Email, row.Username, row.APIKeyID, row.ServiceID, row.ResourceType, row.Unit, strconv.FormatInt(row.Quantity, 10), strconv.FormatInt(row.Credits, 10), row.Status, row.IdempotencyKey, compactJSON(row.Metadata)})
	}
	writer.Flush()
}

func (s *Server) handleAdminLedger(w http.ResponseWriter, r *http.Request) {
	if !s.internalAllowed(w, r) || !s.databaseAvailable(w) {
		return
	}
	query := parseAdminAuditQuery(r)
	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()
	rows, total, err := s.adminLedgerRows(ctx, query)
	if err != nil {
		s.logDatabaseError("admin_ledger", err)
		writeAPIError(w, http.StatusInternalServerError, "admin_ledger_failed", "could not load ledger rows")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"contractVersion": "user-billing-admin-ledger-v1", "rows": rows, "count": len(rows), "total": total, "pagination": pagination(query.Page, query.PageSize, total), "filters": auditFilterSummary(query), "valuesRedacted": true})
}

func (s *Server) handleAdminLedgerExport(w http.ResponseWriter, r *http.Request) {
	if !s.internalAllowed(w, r) || !s.databaseAvailable(w) {
		return
	}
	query := parseAdminAuditQuery(r)
	query.Page = 1
	query.PageSize = queryLimit(r, "limit", 1000, 5000)
	ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()
	rows, _, err := s.adminLedgerRows(ctx, query)
	if err != nil {
		s.logDatabaseError("admin_ledger_export", err)
		writeAPIError(w, http.StatusInternalServerError, "admin_ledger_export_failed", "could not export ledger rows")
		return
	}
	w.Header().Set("Content-Type", "text/csv; charset=utf-8")
	w.Header().Set("Content-Disposition", `attachment; filename="hwlab-admin-ledger.csv"`)
	writer := csv.NewWriter(w)
	_ = writer.Write([]string{"id", "created_at", "user_id", "email", "username", "kind", "reason", "source", "status", "delta_credits", "balance_before", "balance_after", "operator_user_id", "operator_username", "idempotency_key", "metadata_json"})
	for _, row := range rows {
		before := ""
		if row.BalanceBefore != nil {
			before = strconv.FormatInt(*row.BalanceBefore, 10)
		}
		_ = writer.Write([]string{row.ID, row.CreatedAt.Format(time.RFC3339), row.UserID, row.Email, row.Username, row.Kind, row.Reason, row.Source, row.Status, strconv.FormatInt(row.DeltaCredits, 10), before, strconv.FormatInt(row.BalanceAfter, 10), row.OperatorUserID, row.OperatorUsername, row.IdempotencyKey, compactJSON(row.Metadata)})
	}
	writer.Flush()
}

func (s *Server) adminUsageRows(ctx context.Context, query adminAuditQuery) ([]adminUsageRow, int64, error) {
	where, args := adminUsageWhere(query)
	total, err := countRows(ctx, s.db, `SELECT COUNT(*) FROM hwlab_usage_records ur JOIN hwlab_users u ON u.id = ur.user_id `+where, args)
	if err != nil {
		return nil, 0, err
	}
	rowArgs := append([]any{}, args...)
	rowArgs = append(rowArgs, query.PageSize, (query.Page-1)*query.PageSize)
	limitParam := "$" + strconv.Itoa(len(rowArgs)-1)
	offsetParam := "$" + strconv.Itoa(len(rowArgs))
	rows, err := s.db.QueryContext(ctx, `SELECT ur.id, COALESCE(ur.reservation_id, ''), ur.user_id, u.email, u.username, COALESCE(ur.api_key_id, ''), ur.service_id, ur.resource_type, ur.unit, ur.quantity, ur.credits, ur.status, COALESCE(ur.idempotency_key, ''), ur.metadata::text, ur.created_at FROM hwlab_usage_records ur JOIN hwlab_users u ON u.id = ur.user_id `+where+` ORDER BY ur.created_at DESC, ur.id DESC LIMIT `+limitParam+` OFFSET `+offsetParam, rowArgs...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()
	items := []adminUsageRow{}
	for rows.Next() {
		var item adminUsageRow
		var metadataRaw string
		if err := rows.Scan(&item.ID, &item.ReservationID, &item.UserID, &item.Email, &item.Username, &item.APIKeyID, &item.ServiceID, &item.ResourceType, &item.Unit, &item.Quantity, &item.Credits, &item.Status, &item.IdempotencyKey, &metadataRaw, &item.CreatedAt); err != nil {
			return nil, 0, err
		}
		item.Metadata = redactedMetadata(parseJSONMap(metadataRaw))
		items = append(items, item)
	}
	return items, total, rows.Err()
}

func (s *Server) adminLedgerRows(ctx context.Context, query adminAuditQuery) ([]adminLedgerRow, int64, error) {
	where, args := adminLedgerWhere(query)
	total, err := countRows(ctx, s.db, `SELECT COUNT(*) FROM hwlab_credit_ledger l JOIN hwlab_users u ON u.id = l.user_id `+where, args)
	if err != nil {
		return nil, 0, err
	}
	rowArgs := append([]any{}, args...)
	rowArgs = append(rowArgs, query.PageSize, (query.Page-1)*query.PageSize)
	limitParam := "$" + strconv.Itoa(len(rowArgs)-1)
	offsetParam := "$" + strconv.Itoa(len(rowArgs))
	rows, err := s.db.QueryContext(ctx, `SELECT l.id, l.user_id, u.email, u.username, l.kind, l.reason, l.delta_credits, l.balance_before, l.balance_after, l.source, l.status, COALESCE(l.idempotency_key, ''), COALESCE(l.operator_user_id, ''), COALESCE(l.operator_username, ''), l.metadata::text, l.created_at FROM hwlab_credit_ledger l JOIN hwlab_users u ON u.id = l.user_id `+where+` ORDER BY l.created_at DESC, l.id DESC LIMIT `+limitParam+` OFFSET `+offsetParam, rowArgs...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()
	items := []adminLedgerRow{}
	for rows.Next() {
		var item adminLedgerRow
		var before sql.NullInt64
		var metadataRaw string
		if err := rows.Scan(&item.ID, &item.UserID, &item.Email, &item.Username, &item.Kind, &item.Reason, &item.DeltaCredits, &before, &item.BalanceAfter, &item.Source, &item.Status, &item.IdempotencyKey, &item.OperatorUserID, &item.OperatorUsername, &metadataRaw, &item.CreatedAt); err != nil {
			return nil, 0, err
		}
		if before.Valid {
			value := before.Int64
			item.BalanceBefore = &value
		}
		item.Metadata = redactedMetadata(parseJSONMap(metadataRaw))
		items = append(items, item)
	}
	return items, total, rows.Err()
}

func adminUsageWhere(query adminAuditQuery) (string, []any) {
	clauses, args := []string{}, []any{}
	addAuditClause(&clauses, &args, "ur.user_id = %s", query.UserID)
	addAuditClause(&clauses, &args, "ur.service_id = %s", query.ServiceID)
	addAuditClause(&clauses, &args, "ur.resource_type = %s", query.ResourceType)
	addAuditClause(&clauses, &args, "ur.api_key_id = %s", query.APIKeyID)
	addAuditClause(&clauses, &args, "ur.status = %s", query.Status)
	addAuditClause(&clauses, &args, "ur.idempotency_key = %s", query.IdempotencyKey)
	addAuditTimeClause(&clauses, &args, "ur.created_at >= %s::timestamptz", query.From)
	addAuditTimeClause(&clauses, &args, "ur.created_at <= %s::timestamptz", query.To)
	if search := strings.TrimSpace(strings.ToLower(query.Search)); search != "" {
		args = append(args, "%"+search+"%")
		param := "$" + strconv.Itoa(len(args))
		clauses = append(clauses, "(lower(ur.id) LIKE "+param+" OR lower(ur.service_id) LIKE "+param+" OR lower(ur.resource_type) LIKE "+param+" OR lower(u.email) LIKE "+param+" OR lower(u.username) LIKE "+param+")")
	}
	return whereSQL(clauses), args
}

func adminLedgerWhere(query adminAuditQuery) (string, []any) {
	clauses, args := []string{}, []any{}
	addAuditClause(&clauses, &args, "l.user_id = %s", query.UserID)
	addAuditClause(&clauses, &args, "l.kind = %s", query.Kind)
	addAuditClause(&clauses, &args, "l.source = %s", query.Source)
	addAuditClause(&clauses, &args, "l.status = %s", query.Status)
	addAuditClause(&clauses, &args, "l.idempotency_key = %s", query.IdempotencyKey)
	addAuditTimeClause(&clauses, &args, "l.created_at >= %s::timestamptz", query.From)
	addAuditTimeClause(&clauses, &args, "l.created_at <= %s::timestamptz", query.To)
	if search := strings.TrimSpace(strings.ToLower(query.Search)); search != "" {
		args = append(args, "%"+search+"%")
		param := "$" + strconv.Itoa(len(args))
		clauses = append(clauses, "(lower(l.id) LIKE "+param+" OR lower(l.kind) LIKE "+param+" OR lower(l.reason) LIKE "+param+" OR lower(u.email) LIKE "+param+" OR lower(u.username) LIKE "+param+")")
	}
	return whereSQL(clauses), args
}

func parseAdminAuditQuery(r *http.Request) adminAuditQuery {
	return adminAuditQuery{Page: queryLimit(r, "page", 1, 100000), PageSize: queryLimit(r, "pageSize", 50, 500), UserID: textQuery(r, "userId"), Search: textQuery(r, "search"), ServiceID: textQuery(r, "serviceId"), ResourceType: textQuery(r, "resourceType"), APIKeyID: textQuery(r, "apiKeyId"), Status: textQuery(r, "status"), Kind: textQuery(r, "kind"), Source: textQuery(r, "source"), IdempotencyKey: textQuery(r, "idempotencyKey"), From: textQuery(r, "from"), To: textQuery(r, "to")}
}

func addAuditClause(clauses *[]string, args *[]any, template, value string) {
	if value = strings.TrimSpace(value); value != "" {
		*args = append(*args, value)
		*clauses = append(*clauses, fmt.Sprintf(template, "$"+strconv.Itoa(len(*args))))
	}
}

func addAuditTimeClause(clauses *[]string, args *[]any, template, value string) {
	addAuditClause(clauses, args, template, value)
}

func whereSQL(clauses []string) string {
	if len(clauses) == 0 {
		return ""
	}
	return "WHERE " + strings.Join(clauses, " AND ")
}

func countRows(ctx context.Context, db *sql.DB, query string, args []any) (int64, error) {
	var total int64
	err := db.QueryRowContext(ctx, query, args...).Scan(&total)
	return total, err
}

func pagination(page, pageSize int, total int64) map[string]any {
	totalPages := int64(1)
	if pageSize > 0 && total > 0 {
		totalPages = (total + int64(pageSize) - 1) / int64(pageSize)
	}
	return map[string]any{"page": page, "pageSize": pageSize, "total": total, "totalPages": totalPages}
}

func auditFilterSummary(query adminAuditQuery) map[string]any {
	return map[string]any{"userId": query.UserID, "search": query.Search, "serviceId": query.ServiceID, "resourceType": query.ResourceType, "apiKeyId": query.APIKeyID, "status": query.Status, "kind": query.Kind, "source": query.Source, "idempotencyKey": query.IdempotencyKey, "from": query.From, "to": query.To}
}

func textQuery(r *http.Request, name string) string {
	return strings.TrimSpace(r.URL.Query().Get(name))
}

func compactJSON(value map[string]any) string {
	if len(value) == 0 {
		return "{}"
	}
	body, err := json.Marshal(value)
	if err != nil {
		return "{}"
	}
	return string(body)
}

func redactedMetadata(input map[string]any) map[string]any {
	if len(input) == 0 {
		return map[string]any{}
	}
	output := map[string]any{}
	for key, value := range input {
		lower := strings.ToLower(key)
		if strings.Contains(lower, "token") || strings.Contains(lower, "secret") || strings.Contains(lower, "password") || strings.Contains(lower, "database_url") || strings.Contains(lower, "dsn") || strings.Contains(lower, "key_hash") || strings.Contains(lower, "apikey") || strings.Contains(lower, "api_key") {
			output[key] = "redacted"
			continue
		}
		if nested, ok := value.(map[string]any); ok {
			output[key] = redactedMetadata(nested)
			continue
		}
		output[key] = value
	}
	return output
}
