package clickhouse 

import (
	"errors"
	
	"openreplay/backend/pkg/hashid"
	"openreplay/backend/pkg/url"
	. "openreplay/backend/pkg/db/types"
	. "openreplay/backend/pkg/messages"
)


func (conn *Connector) InsertWebSession(session *Session) error {
	if (session.Duration == nil) {
		return errors.New("Clickhouse: trying to insert session with ")
	}

	if err := conn.sessions.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion,
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(session.Timestamp),
		uint32(*session.Duration),
		session.PagesCount,
		session.EventsCount,
		session.ErrorsCount,
		// Web unique columns
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
	); err != nil {
		return err
	}
	// TODO: join sessions, sessions_metadata & sessions_ios
	return conn.metadata.exec(
		session.SessionID,
		session.UserID,
		session.UserAnonymousID,
		session.Metadata1,
		session.Metadata2,
		session.Metadata3,
		session.Metadata4,
		session.Metadata5,
		session.Metadata6,
		session.Metadata7,
		session.Metadata8,
		session.Metadata9,
		session.Metadata10,
		datetime(session.Timestamp),
	)
}

func (conn *Connector) InsertWebResourceEvent(session *Session, msg *ResourceEvent) error {
	// nullableString causes error  "unexpected type *string" on Nullable Enum type
	// (apparently, a clickhouse-go bug) https://github.com/ClickHouse/clickhouse-go/pull/204
	var method interface{} = url.EnsureMethod(msg.Method)
	if method == "" {
		method = nil
	}
	return conn.resources.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion, 
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(msg.Timestamp),
		url.DiscardURLQuery(msg.URL),
		msg.Type,
		nullableUint16(uint16(msg.Duration)),
		nullableUint16(uint16(msg.TTFB)),
		nullableUint16(uint16(msg.HeaderSize)),
		nullableUint32(uint32(msg.EncodedBodySize)),
		nullableUint32(uint32(msg.DecodedBodySize)),
		msg.Success,
		method, 
		nullableUint16(uint16(msg.Status)),
	)
}

func (conn *Connector) InsertWebPageEvent(session *Session, msg *PageEvent) error {
	return conn.pages.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion, nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(msg.Timestamp),
		url.DiscardURLQuery(msg.URL),
		nullableUint16(uint16(msg.RequestStart)),
		nullableUint16(uint16(msg.ResponseStart)),
		nullableUint16(uint16(msg.ResponseEnd)),
		nullableUint16(uint16(msg.DomContentLoadedEventStart)),
		nullableUint16(uint16(msg.DomContentLoadedEventEnd)),
		nullableUint16(uint16(msg.LoadEventStart)),
		nullableUint16(uint16(msg.LoadEventEnd)),
		nullableUint16(uint16(msg.FirstPaint)),
		nullableUint16(uint16(msg.FirstContentfulPaint)),
		nullableUint16(uint16(msg.SpeedIndex)),
		nullableUint16(uint16(msg.VisuallyComplete)),
		nullableUint16(uint16(msg.TimeToInteractive)),
	)
}

func (conn *Connector) InsertWebClickEvent(session *Session, msg *ClickEvent) error {
	if msg.Label == "" {
		return nil
	}
	return conn.clicks.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion,
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(msg.Timestamp),
		msg.Label,
		nullableUint32(uint32(msg.HesitationTime)),
	)
}

func (conn *Connector) InsertWebInputEvent(session *Session, msg *InputEvent) error {
	if msg.Label == "" {
		return nil
	}
	return conn.inputs.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion,
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(msg.Timestamp),
		msg.Label,
	)
}

func (conn *Connector) InsertWebErrorEvent(session *Session, msg *ErrorEvent) error {
	return conn.errors.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion,
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(msg.Timestamp),
		msg.Source,
		nullableString(msg.Name),
		msg.Message,
		hashid.WebErrorID(session.ProjectID, msg),
	)
}

func (conn *Connector) InsertWebPerformanceTrackAggr(session *Session, msg *PerformanceTrackAggr) error {
	var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2
	return conn.performance.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion,
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(timestamp),
		uint8(msg.MinFPS),
		uint8(msg.AvgFPS),
		uint8(msg.MaxFPS),
		uint8(msg.MinCPU),
		uint8(msg.AvgCPU),
		uint8(msg.MaxCPU),
		msg.MinTotalJSHeapSize,
		msg.AvgTotalJSHeapSize,
		msg.MaxTotalJSHeapSize,
		msg.MinUsedJSHeapSize,
		msg.AvgUsedJSHeapSize,
		msg.MaxUsedJSHeapSize,
	)
}

// TODO: make enum message type
var CONTEXT_MAP = map[uint64]string{0:"unknown",1:"self",2:"same-origin-ancestor",3:"same-origin-descendant",4:"same-origin",5:"cross-origin-ancestor",6:"cross-origin-descendant",7:"cross-origin-unreachable",8:"multiple-contexts"}
var CONTAINER_TYPE_MAP = map[uint64]string{0:"window",1:"iframe",2:"embed",3:"object"}

func (conn *Connector) InsertLongtask(session *Session, msg *LongTask) error {
	return conn.longtasks.exec(
		session.SessionID,
		session.ProjectID,
		session.TrackerVersion,
		nullableString(session.RevID),
		session.UserUUID,
		session.UserOS,
		nullableString(session.UserOSVersion),
		session.UserBrowser,
		nullableString(session.UserBrowserVersion),
		nullableString(session.UserDevice),
		session.UserDeviceType,
		session.UserCountry,
		datetime(msg.Timestamp),
		CONTEXT_MAP[ msg.Context ],
		CONTAINER_TYPE_MAP[ msg.ContainerType ],
		msg.ContainerId,
		msg.ContainerName,
		msg.ContainerSrc,
	)
}
