package services

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/nexuscrm/audit"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

const auditLogExportEmitTimeout = 10 * time.Second

const (
	envAuditLogExportMetaEnabled   = "AUDIT_EXPORT_META_ENABLED"
	envAuditLogExportMetaThreshold = "AUDIT_EXPORT_META_THRESHOLD"
	defaultAuditLogExportThreshold = 100
	auditLogSelfObject             = "_System_AuditLog"
)

type auditLogContract struct {
	db      *sql.DB
	emitter *audit.DBEmitter
	reader  contract.AuditLog
}

func (sm *ServiceManager) AuditLogContract() contract.AuditLog {
	if sm == nil {
		return nil
	}
	if sm.NeutralAuditLog != nil {
		return sm.NeutralAuditLog
	}
	if sm.DB() == nil {
		return nil
	}
	return &auditLogContract{
		db:      sm.DB(),
		emitter: audit.NewDBEmitter(),
		reader:  audit.NewReader(sm.DB()),
	}
}

func (a *auditLogContract) QueryAuditLog(ctx context.Context, req contract.AuditLogQueryRequest, user *models.UserSession) (*contract.AuditLogQueryResult, error) {
	if a == nil || a.reader == nil {
		return nil, &contract.AuditLogError{
			Kind:    contract.AuditLogErrorUnavailable,
			Message: "audit log backend not initialized",
		}
	}
	result, err := a.reader.QueryAuditLog(ctx, req, user)
	if err != nil {
		return nil, err
	}
	var userID string
	if user != nil {
		userID = user.ID
	}
	a.dispatchExportAudit(ctx, userID, auditLogExportFilters{
		Object:   req.ObjectAPIName,
		RecordID: req.RecordID,
		Limit:    req.Limit,
		Offset:   req.Offset,
		Rows:     len(result.Rows),
	})
	return result, nil
}

type auditLogExportFilters struct {
	Object   string `json:"object"`
	RecordID string `json:"recordId"`
	Limit    int    `json:"limit"`
	Offset   int    `json:"offset"`
	Rows     int    `json:"rows"`
}

func auditLogExportMetaEnabled() bool {
	v := strings.TrimSpace(os.Getenv(envAuditLogExportMetaEnabled))
	return strings.EqualFold(v, "true") || v == "1"
}

func auditLogExportMetaThreshold() int {
	raw := strings.TrimSpace(os.Getenv(envAuditLogExportMetaThreshold))
	if raw == "" {
		return defaultAuditLogExportThreshold
	}
	n, err := strconv.Atoi(raw)
	if err != nil || n <= 0 {
		return defaultAuditLogExportThreshold
	}
	return n
}

func auditLogFilterHash(f auditLogExportFilters) string {
	raw := fmt.Sprintf("%s|%s|%d|%d", f.Object, f.RecordID, f.Limit, f.Offset)
	sum := sha256.Sum256([]byte(raw))
	return hex.EncodeToString(sum[:8])
}

func (a *auditLogContract) dispatchExportAudit(ctx context.Context, userID string, filters auditLogExportFilters) {
	go a.emitExportAuditAsync(ctx, userID, filters)
}

func (a *auditLogContract) emitExportAuditAsync(ctx context.Context, userID string, filters auditLogExportFilters) {
	defer recoverAuditLogExportAsyncPanic(userID, filters)

	emitCtx, cancel := auditLogExportAsyncContext(ctx)
	defer cancel()
	a.emitExportAudit(emitCtx, userID, filters)
}

func recoverAuditLogExportAsyncPanic(userID string, filters auditLogExportFilters) {
	recovered := recover()
	if recovered == nil {
		return
	}
	log.Error().
		Interface("panic", recovered).
		Str("user", userID).
		Str("object", filters.Object).
		Str("record", filters.RecordID).
		Bytes("stack", debug.Stack()).
		Msg("Recovered from async audit export meta-audit panic")
}

func auditLogExportAsyncContext(ctx context.Context) (context.Context, context.CancelFunc) {
	traceparent, requestID := auditLogExportAsyncObservability(ctx)
	asyncCtx := context.Background()
	if traceparent != "" {
		asyncCtx = context.WithValue(asyncCtx, constants.ContextKeyTraceparent, traceparent)
	}
	if requestID != "" {
		asyncCtx = context.WithValue(asyncCtx, constants.ContextKeyRequestID, requestID)
	}
	return context.WithTimeout(asyncCtx, auditLogExportEmitTimeout)
}

func auditLogExportAsyncObservability(ctx context.Context) (string, string) {
	if ctx == nil {
		return "", ""
	}
	traceparent, _ := ctx.Value(constants.ContextKeyTraceparent).(string)
	requestID, _ := ctx.Value(constants.ContextKeyRequestID).(string)
	return traceparent, requestID
}

func (a *auditLogContract) emitExportAudit(ctx context.Context, userID string, filters auditLogExportFilters) {
	if !auditLogExportMetaEnabled() {
		return
	}
	if filters.Rows < auditLogExportMetaThreshold() {
		return
	}
	if a == nil || a.db == nil || a.emitter == nil {
		log.Debug().Msg("EXPORT_AUDIT meta-audit skipped: db or emitter not configured")
		return
	}
	if strings.TrimSpace(userID) == "" {
		log.Debug().Msg("EXPORT_AUDIT meta-audit skipped: no userID in context")
		return
	}

	detail, err := json.Marshal(filters)
	if err != nil {
		log.Warn().Err(err).Msg("EXPORT_AUDIT meta-audit: marshal filters failed; skipping")
		return
	}

	event := audit.AuditEvent{
		ObjectApiName:    auditLogSelfObject,
		RecordId:         auditLogFilterHash(filters),
		Action:           audit.ActionExportAudit,
		ChangedById:      userID,
		Entrypoint:       audit.EntrypointRestAPI,
		EntrypointDetail: string(detail),
		Outcome:          audit.OutcomeSuccess,
	}

	tx, err := a.db.BeginTx(ctx, nil)
	if err != nil {
		log.Warn().Err(err).Msg("EXPORT_AUDIT meta-audit: begin tx failed; export unaffected")
		return
	}
	committed := false
	defer func() {
		if !committed {
			_ = tx.Rollback()
		}
	}()
	if err := a.emitter.EmitTx(ctx, tx, event); err != nil {
		log.Warn().Err(err).Msg("EXPORT_AUDIT meta-audit: emit failed; export unaffected")
		return
	}
	if err := tx.Commit(); err != nil {
		log.Warn().Err(err).Msg("EXPORT_AUDIT meta-audit: commit failed; export unaffected")
		return
	}
	committed = true
}
