package handler

import (
	"baron-sso-backend/internal/domain"
	"encoding/base64"
	"errors"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
)

type AuditHandler struct {
	repo domain.AuditRepository
}

func NewAuditHandler(repo domain.AuditRepository) *AuditHandler {
	return &AuditHandler{repo: repo}
}

// CreateLog handles POST /api/v1/audit
func (h *AuditHandler) CreateLog(c *fiber.Ctx) error {
	var req domain.AuditLog
	if err := c.BodyParser(&req); err != nil {
		return errorJSON(c, fiber.StatusBadRequest, "Cannot parse JSON")
	}

	// Auto-fill metadata if missing
	if req.IPAddress == "" {
		req.IPAddress = c.IP()
	}
	if req.UserAgent == "" {
		req.UserAgent = c.Get("User-Agent")
	}
	if req.Timestamp.IsZero() {
		req.Timestamp = time.Now()
	}
	if req.EventID == "" {
		req.EventID = ensureRequestID(c)
	}

	if h.repo == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "Audit service unavailable")
	}

	if err := h.repo.Create(&req); err != nil {
		// Log internal error but don't expose details
		return errorJSON(c, fiber.StatusInternalServerError, "Failed to save audit log")
	}

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"message": "Audit log saved",
	})
}

// ListLogs handles GET /api/v1/audit
func (h *AuditHandler) ListLogs(c *fiber.Ctx) error {
	limit := c.QueryInt("limit", 50)
	cursorRaw := c.Query("cursor")
	requestedTenantID := c.Query("tenantId")

	cursor, err := parseAuditCursor(cursorRaw)
	if err != nil {
		return errorJSON(c, fiber.StatusBadRequest, "Invalid cursor")
	}

	if h.repo == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "Audit service unavailable")
	}

	// [New] Role-based Filtering
	profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
	var filterTenantID string

	if profile != nil {
		if profile.Role == domain.RoleSuperAdmin {
			// Super Admin can see everything or filter by a specific tenant if requested
			filterTenantID = requestedTenantID
		} else if profile.Role == domain.RoleTenantAdmin {
			// Tenant Admin can only see their own tenant logs (or manageable ones)
			// For now, lock to their primary tenant or requested one IF it's in their manageable list
			if profile.TenantID != nil {
				filterTenantID = *profile.TenantID
			}

			// If they requested a specific tenant, verify they can manage it
			if requestedTenantID != "" && requestedTenantID != filterTenantID {
				canManage := false
				for _, t := range profile.ManageableTenants {
					if t.ID == requestedTenantID {
						canManage = true
						break
					}
				}
				if !canManage {
					return errorJSON(c, fiber.StatusForbidden, "forbidden: cannot view logs for this tenant")
				}
				filterTenantID = requestedTenantID
			}
		} else {
			return errorJSON(c, fiber.StatusForbidden, "forbidden")
		}
	}

	logs, err := h.repo.FindPage(c.Context(), limit+1, cursor, filterTenantID)
	if err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, "Failed to retrieve audit logs")
	}

	nextCursor := ""
	if len(logs) > limit {
		last := logs[limit-1]
		nextCursor = encodeAuditCursor(last)
		logs = logs[:limit]
	}

	return c.JSON(fiber.Map{
		"items":       logs,
		"limit":       limit,
		"cursor":      cursorRaw,
		"next_cursor": nextCursor,
	})
}

func ensureRequestID(c *fiber.Ctx) string {
	reqID := c.Get("X-Request-Id")
	if reqID == "" {
		reqID = uuid.New().String()
		c.Set("X-Request-Id", reqID)
	}
	return reqID
}

func parseAuditCursor(raw string) (*domain.AuditCursor, error) {
	if raw == "" {
		return nil, nil
	}
	decoded, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return nil, err
	}
	parts := strings.SplitN(string(decoded), "|", 2)
	if len(parts) != 2 {
		return nil, errors.New("invalid cursor")
	}
	ts, err := time.Parse(time.RFC3339Nano, parts[0])
	if err != nil {
		return nil, err
	}
	return &domain.AuditCursor{
		Timestamp: ts,
		EventID:   parts[1],
	}, nil
}

func encodeAuditCursor(log domain.AuditLog) string {
	payload := log.Timestamp.UTC().Format(time.RFC3339Nano) + "|" + log.EventID
	return base64.RawURLEncoding.EncodeToString([]byte(payload))
}
