package audit

import (
	"fmt"

	controltowerv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/controltower/v1"
	"google.golang.org/protobuf/types/known/durationpb"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func (s *cloudSink) translateToPmgEvents(event AuditEvent) []*controltowerv1.PmgEvent {
	switch event.Type {
	case EventTypeMalwareBlocked:
		return []*controltowerv1.PmgEvent{newPackageDecisionEvent(event, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_BLOCKED)}
	case EventTypeMalwareConfirmed:
		return []*controltowerv1.PmgEvent{newPackageDecisionEvent(event, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_CONFIRMED)}
	case EventTypeCooldownSkipped:
		return []*controltowerv1.PmgEvent{newPackageDecisionEvent(event, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_COOLDOWN_SKIPPED)}
	case EventTypeInstallTrustedAllowed:
		return []*controltowerv1.PmgEvent{newPackageDecisionEvent(event, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_TRUSTED)}
	case EventTypeInstallInsecureBypass:
		// PmgInsecureBypass is a session-level aggregate (package manager + total bypassed count),
		// not a per-package event. It is emitted as part of EventTypeSessionComplete when
		// the session's insecureBypassed counter is > 0.
		return nil
	case EventTypeDependencyCooldown:
		return []*controltowerv1.PmgEvent{newCooldownBlockedEvent(event)}
	case EventTypeProxyHostObserved:
		return []*controltowerv1.PmgEvent{newHostObservationEvent(event)}
	case EventTypeSandboxOverride:
		return []*controltowerv1.PmgEvent{newSandboxOverrideEvent(event)}
	case EventTypeError:
		return []*controltowerv1.PmgEvent{newErrorEvent(event)}
	case EventTypeSessionComplete:
		if event.SessionData == nil {
			return nil
		}
		events := []*controltowerv1.PmgEvent{newSessionSummaryEvent(event.SessionData)}
		if event.SessionData.InsecureBypassed > 0 {
			events = append(events, newInsecureBypassFromSession(event.SessionData))
		}
		return events
	default:
		return nil
	}
}

func newPackageDecisionEvent(event AuditEvent, action controltowerv1.PmgPackageAction) *controltowerv1.PmgEvent {
	decision := &controltowerv1.PmgPackageDecision{}
	decision.SetPackageVersion(event.PackageVersion)
	decision.SetAction(action)

	if event.AnalysisID != "" {
		decision.SetAnalysisId(event.AnalysisID)
	}
	decision.SetIsMalware(event.IsMalware)
	decision.SetIsVerified(event.IsVerified)

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION)
	e.SetPackageDecision(decision)
	return e
}

func newSandboxOverrideEvent(event AuditEvent) *controltowerv1.PmgEvent {
	override := &controltowerv1.PmgSandboxOverride{}
	override.SetSandboxProfile(event.ProfileName)

	var flattened []string
	for _, m := range event.Overrides {
		for k, v := range m {
			flattened = append(flattened, fmt.Sprintf("%s:%s", k, v))
		}
	}
	override.SetOverrides(flattened)

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_SANDBOX_OVERRIDE)
	e.SetSandboxOverride(override)
	return e
}

func newErrorEvent(event AuditEvent) *controltowerv1.PmgEvent {
	pmgErr := &controltowerv1.PmgError{}
	if event.Error != nil {
		pmgErr.SetErrorType(fmt.Sprintf("%T", event.Error))
	}
	pmgErr.SetMessage(event.Message)

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_ERROR)
	e.SetError(pmgErr)
	return e
}

func newCooldownBlockedEvent(event AuditEvent) *controltowerv1.PmgEvent {
	decision := &controltowerv1.PmgPackageDecision{}
	decision.SetPackageVersion(event.PackageVersion)
	decision.SetAction(controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_COOLDOWN_BLOCKED)

	cooldown := &controltowerv1.PmgDependencyCooldown{}
	if !event.PublishDate.IsZero() {
		cooldown.SetPublishDate(timestamppb.New(event.PublishDate))
	}
	cooldown.SetCooldownDays(uint32(event.CooldownDays))
	cooldown.SetDaysSincePublish(uint32(event.DaysAgo))
	cooldown.SetDaysRemaining(uint32(event.DaysLeft))
	decision.SetCooldown(cooldown)

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION)
	e.SetPackageDecision(decision)
	return e
}

func newHostObservationEvent(event AuditEvent) *controltowerv1.PmgEvent {
	obs := &controltowerv1.PmgHostObservation{}
	obs.SetHostname(event.Hostname)
	obs.SetMethod(event.Method)

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_HOST_OBSERVATION)
	e.SetHostObservation(obs)
	return e
}

func newSessionSummaryEvent(data *SessionData) *controltowerv1.PmgEvent {
	summary := &controltowerv1.PmgSessionSummary{}
	summary.SetPackageManager(mapPackageManager(data.PackageManager))
	summary.SetFlowType(mapFlowType(data.FlowType))
	summary.SetTotalAnalyzed(data.TotalAnalyzed)
	summary.SetAllowedCount(data.AllowedCount)
	summary.SetBlockedCount(data.BlockedCount)
	summary.SetConfirmedCount(data.ConfirmedCount)
	summary.SetTrustedSkipped(data.TrustedSkipped)
	summary.SetCooldownBlockedCount(data.CooldownBlockedCount)
	summary.SetDuration(durationpb.New(data.Duration))
	summary.SetSandboxEnabled(data.SandboxEnabled)
	summary.SetParanoidMode(data.ParanoidMode)
	summary.SetTransitiveEnabled(data.TransitiveEnabled)
	summary.SetOutcome(mapSessionOutcome(data.Outcome))

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_SESSION_SUMMARY)
	e.SetSessionSummary(summary)
	return e
}

func newInsecureBypassFromSession(data *SessionData) *controltowerv1.PmgEvent {
	bypass := &controltowerv1.PmgInsecureBypass{}
	bypass.SetPackageManager(mapPackageManager(data.PackageManager))
	bypass.SetPackagesBypassed(data.InsecureBypassed)

	e := &controltowerv1.PmgEvent{}
	e.SetEventType(controltowerv1.PmgEventType_PMG_EVENT_TYPE_INSECURE_BYPASS)
	e.SetInsecureBypass(bypass)
	return e
}

func mapFlowType(ft FlowType) controltowerv1.PmgFlowType {
	switch ft {
	case FlowTypeGuard:
		return controltowerv1.PmgFlowType_PMG_FLOW_TYPE_GUARD
	case FlowTypeProxy:
		return controltowerv1.PmgFlowType_PMG_FLOW_TYPE_PROXY
	default:
		return controltowerv1.PmgFlowType_PMG_FLOW_TYPE_UNSPECIFIED
	}
}

func mapSessionOutcome(outcome Outcome) controltowerv1.PmgSessionOutcome {
	switch outcome {
	case OutcomeSuccess:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_SUCCESS
	case OutcomeBlocked:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_BLOCKED
	case OutcomeUserCancelled:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_USER_CANCELLED
	case OutcomeError:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_ERROR
	case OutcomeDryRun:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_DRY_RUN
	case OutcomeInsecureBypass:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_INSECURE_BYPASS
	default:
		return controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_UNSPECIFIED
	}
}

func mapPackageManager(name string) controltowerv1.PmgPackageManager {
	switch name {
	case "npm", "npx":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_NPM
	case "pnpm", "pnpx":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PNPM
	case "yarn":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_YARN
	case "bun":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_BUN
	case "pip", "pip3", "pipx":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PIP
	case "poetry":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_POETRY
	case "uv", "uvx":
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_UV
	default:
		return controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_UNSPECIFIED
	}
}
