// Copyright 2019-2024 The Inspektor Gadget authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package types

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns"
)

type EventType string

var node string

func init() {
	// Register column templates
	columns.MustRegisterTemplate("timestamp", "width:35,maxWidth:35,hide")
	columns.MustRegisterTemplate("node", "width:30,ellipsis:middle")
	columns.MustRegisterTemplate("namespace", "width:30")
	columns.MustRegisterTemplate("pod", "width:30,ellipsis:middle")
	columns.MustRegisterTemplate("container", "width:30")
	columns.MustRegisterTemplate("containerPid", "width:6,hide")
	columns.MustRegisterTemplate("containerImageName", "width:30")
	columns.MustRegisterTemplate("containerImageDigest", "width:30")
	columns.MustRegisterTemplate("containerImageID", "width:30")
	columns.MustRegisterTemplate("containerStartedAt", "width:35,hide")
	columns.MustRegisterTemplate("comm", "maxWidth:16")
	columns.MustRegisterTemplate("pid", "minWidth:7")
	columns.MustRegisterTemplate("uid", "minWidth:8")
	columns.MustRegisterTemplate("gid", "minWidth:8")
	columns.MustRegisterTemplate("ns", "width:12,hide")

	// For IPs (IPv4+IPv6):
	// Min: XXX.XXX.XXX.XXX (IPv4) = 15
	// Max: 0000:0000:0000:0000:0000:ffff:XXX.XXX.XXX.XXX (IPv4-mapped IPv6 address) = 45
	columns.MustRegisterTemplate("ipaddr", "minWidth:15,maxWidth:45")
	columns.MustRegisterTemplate("ipport", "minWidth:type")
	// Assume type width for ipport is 5 characters long. Delimiter is 1. Add that to ipaddr template
	columns.MustRegisterTemplate("ipaddrport", "minWidth:22,width:40,maxWidth:52")
	columns.MustRegisterTemplate("ipversion", "width:2,fixed")

	// For system calls as the longest is sched_rr_get_interval_time64 with 28
	// characters:
	// https://gist.github.com/alban/aa664b3c46aaf24aeb69caae29a01ae5
	// But there is a lot of system calls which name is below 18 characters.
	columns.MustRegisterTemplate("syscall", "width:18,maxWidth:28")
}

type Time int64

func (t Time) String() string {
	// Don't use time.RFC3339Nano because we prefer to keep the trailing
	// zeros for alignment
	return time.Unix(0, int64(t)).Format("2006-01-02T15:04:05.000000000Z07:00")
}

type EndpointKind string

const (
	EndpointKindPod     EndpointKind = "pod"
	EndpointKindService EndpointKind = "svc"
	EndpointKindRaw     EndpointKind = "raw"
)

type RuntimeName string

func (r RuntimeName) String() string {
	return string(r)
}

const (
	RuntimeNameDocker     RuntimeName = "docker"
	RuntimeNameContainerd RuntimeName = "containerd"
	RuntimeNameCrio       RuntimeName = "cri-o"
	RuntimeNamePodman     RuntimeName = "podman"
	RuntimeNameUnknown    RuntimeName = "unknown"
)

func String2RuntimeName(name string) RuntimeName {
	switch name {
	case string(RuntimeNameDocker):
		return RuntimeNameDocker
	case string(RuntimeNameContainerd):
		return RuntimeNameContainerd
	case string(RuntimeNameCrio):
		return RuntimeNameCrio
	case string(RuntimeNamePodman):
		return RuntimeNamePodman
	}
	return RuntimeNameUnknown
}

type Container interface {
	K8sMetadata() *BasicK8sMetadata
	RuntimeMetadata() *BasicRuntimeMetadata
	UsesHostNetwork() bool
	K8sOwnerReference() *K8sOwnerReference
	ContainerPid() uint32
	K8sPodLabelsAsString() string
}

type BasicRuntimeMetadata struct {
	// RuntimeName is the name of the container runtime. It is useful to distinguish
	// who is the "owner" of each container in a list of containers collected
	// from multiple runtimes.
	RuntimeName RuntimeName `json:"runtimeName,omitempty" column:"runtimeName,width:19,fixed,hide"`

	// ContainerID is the container ContainerID without the container runtime prefix. For
	// instance, without the "cri-o://" for CRI-O.
	ContainerID string `json:"containerId,omitempty" column:"containerId,width:13,maxWidth:64,hide"`

	// ContainerName is the container name. In the case the container runtime
	// response with multiple containers, ContainerName contains only the first element.
	ContainerName string `json:"containerName,omitempty" column:"containerName,template:container"`

	// ContainerPID is the process ID of the first process in the container.
	ContainerPID uint32 `json:"containerPid,omitempty" column:"containerPid,template:containerPid"`

	// ContainerImageName is the name of the container image where the event comes from
	// i.e. ghcr.io/inspektor-gadget/ci/busybox:latest
	// Sometimes the image name is not provided by the runtime (i.e. Docker), then ContainerImageName
	// is the imageID
	// i.e. sha256:6e38f40d628db3002f5617342c8872c935de530d867d0f709a2fbda1a302a562
	// OR
	// i.e. 6e38f40d628d, when truncated
	ContainerImageName string `json:"containerImageName,omitempty" column:"containerImageName,hide"`

	// ContainerImageID is the runtime-provided image ID (opaque to users)
	ContainerImageID string `json:"containerImageId,omitempty" column:"containerImageID,hide"`

	// ContainerImageDigest is the (repo) digest of the container image where the event comes from
	// containerd: events from both initial and new containers are enriched
	// crio: events from initial containers are enriched
	ContainerImageDigest string `json:"containerImageDigest,omitempty" column:"containerImageDigest,hide"`

	// ContainerStartedAt is the unix timestamp at which the container was started at
	ContainerStartedAt Time `json:"containerStartedAt,omitempty" column:"containerStartedAt,template:timestamp,stringer,hide"`
}

func (b *BasicRuntimeMetadata) IsEnriched() bool {
	return b.RuntimeName != RuntimeNameUnknown && b.RuntimeName != "" && b.ContainerID != "" && b.ContainerName != "" && b.ContainerImageName != "" && b.ContainerImageDigest != ""
}

type BasicK8sMetadata struct {
	Namespace     string            `json:"namespace,omitempty" column:"namespace,template:namespace"`
	PodName       string            `json:"podName,omitempty" column:"podName,template:pod"`
	PodLabels     map[string]string `json:"podLabels,omitempty" column:"labels,hide"`
	ContainerName string            `json:"containerName,omitempty" column:"containerName,template:container"`
}

type K8sOwnerReference struct {
	Kind string `json:"kind,omitempty" column:"kind,hide"`
	Name string `json:"name,omitempty" column:"name,hide"`
}

func (b *BasicK8sMetadata) IsEnriched() bool {
	return b.Namespace != "" && b.PodName != "" && b.ContainerName != "" && b.PodLabels != nil
}

type K8sMetadata struct {
	Node string `json:"node,omitempty" column:"node,template:node"`

	BasicK8sMetadata `json:",inline"`

	// HostNetwork is true if the container uses the host network namespace
	HostNetwork bool `json:"hostNetwork,omitempty" column:"hostnetwork,hide"`

	Owner K8sOwnerReference `json:"owner,omitempty" column:"owner,hide"`
}

type CommonData struct {
	// Runtime contains the container runtime metadata of the container
	// that generated the event
	Runtime BasicRuntimeMetadata `json:"runtime,omitempty" column:"runtime" columnTags:"runtime"`

	// K8s contains the Kubernetes metadata of the object that generated the
	// event
	K8s K8sMetadata `json:"k8s,omitempty" column:"k8s" columnTags:"kubernetes"`
}

func (k *K8sMetadata) UnmarshalJSON(data []byte) error {
	type Alias K8sMetadata
	aux := &struct {
		PodLabels interface{} `json:"podLabels,omitempty"`
		*Alias
	}{
		Alias: (*Alias)(k),
	}
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.PodLabels == nil {
		return nil
	}

	switch v := aux.PodLabels.(type) {
	case string:
		podLabels, err := parsePodLabels(v)
		if err != nil {
			return err
		}
		k.PodLabels = podLabels
	case map[string]interface{}:
		podLabels := make(map[string]string)
		for key, value := range v {
			strVal, ok := value.(string)
			if !ok {
				return fmt.Errorf("invalid value type for key %s in PodLabels", key)
			}
			podLabels[key] = strVal
		}
		if len(podLabels) != 0 {
			k.PodLabels = podLabels
		}
	default:
		return fmt.Errorf("unexpected type for PodLabels: %T", v)
	}

	return nil
}

type L3Endpoint struct {
	// Addr is filled by the gadget
	Addr    string `json:"addr,omitempty" column:"addr,hide,template:ipaddr"`
	Version uint8  `json:"version,omitempty" column:"v,hide,template:ipversion"`

	// Namespace, Name, Kind and PodLabels get populated by the KubeIPResolver operator
	Namespace string            `json:"namespace,omitempty" column:"ns,template:namespace,hide"`
	Name      string            `json:"podname,omitempty" column:"name,hide"`
	Kind      EndpointKind      `json:"kind,omitempty" column:"kind,hide"`
	PodLabels map[string]string `json:"podlabels,omitempty" column:"podLabels,hide"`

	// PodSelector contains the service's spec.selector when Kind is
	// EndpointKindService. It holds the labels that select the backing pods
	PodSelector map[string]string `json:"podSelector,omitempty" column:"podSelector,hide"`
}

type L4Endpoint struct {
	L3Endpoint
	// Port and Proto are filled by the gadget
	Port  uint16 `json:"port" column:"port,hide,template:ipport"`
	Proto uint16 `json:"proto,omitempty" column:"proto,hide,width:4"`
}

const (
	// Indicates a generic event produced by a gadget. Gadgets extend
	// the base event to contain the specific data the gadget provides
	NORMAL EventType = "normal"

	// Event is an error message
	ERR EventType = "err"

	// Event is a warning message
	WARN EventType = "warn"

	// Event is a debug message
	DEBUG EventType = "debug"

	// Event is a info message
	INFO EventType = "info"

	// Indicates the tracer in the node is now is able to produce events
	READY EventType = "ready"
)

// parsePodLabels parses the podLabels string into a map
func parsePodLabels(s string) (map[string]string, error) {
	if s == "" {
		return nil, nil
	}
	labels := map[string]string{}
	pairs := strings.Split(s, ",")
	for _, pair := range pairs {
		kv := strings.SplitN(pair, "=", 2)
		if len(kv) != 2 {
			return nil, fmt.Errorf("invalid pod labels format: expected key=value, got %s", pair)
		}
		labels[kv[0]] = kv[1]
	}
	return labels, nil
}
