package service

import (
	"context"
	"net/http"
	"strings"
	"time"

	"cmr-backend/internal/apperr"
	"cmr-backend/internal/platform/security"
	"cmr-backend/internal/store/postgres"
)

type EventService struct {
	store *postgres.Store
}

type EventDetailResult struct {
	Event struct {
		ID          string  `json:"id"`
		Slug        string  `json:"slug"`
		DisplayName string  `json:"displayName"`
		Summary     *string `json:"summary,omitempty"`
		Status      string  `json:"status"`
	} `json:"event"`
	Release *struct {
		ID                     string  `json:"id"`
		ConfigLabel            string  `json:"configLabel"`
		ManifestURL            string  `json:"manifestUrl"`
		ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
		RouteCode              *string `json:"routeCode,omitempty"`
	} `json:"release,omitempty"`
	ResolvedRelease      *ResolvedReleaseView      `json:"resolvedRelease,omitempty"`
	Runtime              *RuntimeSummaryView       `json:"runtime,omitempty"`
	CurrentPresentation  *PresentationSummaryView  `json:"currentPresentation,omitempty"`
	CurrentContentBundle *ContentBundleSummaryView `json:"currentContentBundle,omitempty"`
}

type LaunchEventInput struct {
	EventPublicID string
	UserID        string
	ReleaseID     string `json:"releaseId,omitempty"`
	VariantID     string `json:"variantId,omitempty"`
	ClientType    string `json:"clientType"`
	DeviceKey     string `json:"deviceKey"`
}

type LaunchEventResult struct {
	Event struct {
		ID          string `json:"id"`
		DisplayName string `json:"displayName"`
	} `json:"event"`
	Launch struct {
		Source          string                    `json:"source"`
		ResolvedRelease *ResolvedReleaseView      `json:"resolvedRelease,omitempty"`
		Variant         *VariantBindingView       `json:"variant,omitempty"`
		Runtime         *RuntimeSummaryView       `json:"runtime,omitempty"`
		Presentation    *PresentationSummaryView  `json:"presentation,omitempty"`
		ContentBundle   *ContentBundleSummaryView `json:"contentBundle,omitempty"`
		Config          struct {
			ConfigURL            string  `json:"configUrl"`
			ConfigLabel          string  `json:"configLabel"`
			ConfigChecksumSha256 *string `json:"configChecksumSha256,omitempty"`
			ReleaseID            string  `json:"releaseId"`
			RouteCode            *string `json:"routeCode,omitempty"`
		} `json:"config"`
		Business struct {
			Source                string  `json:"source"`
			EventID               string  `json:"eventId"`
			SessionID             string  `json:"sessionId"`
			SessionToken          string  `json:"sessionToken"`
			SessionTokenExpiresAt string  `json:"sessionTokenExpiresAt"`
			RouteCode             *string `json:"routeCode,omitempty"`
		} `json:"business"`
	} `json:"launch"`
}

func NewEventService(store *postgres.Store) *EventService {
	return &EventService{store: store}
}

func (s *EventService) GetEventDetail(ctx context.Context, eventPublicID string) (*EventDetailResult, error) {
	eventPublicID = strings.TrimSpace(eventPublicID)
	if eventPublicID == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "event id is required")
	}

	event, err := s.store.GetEventByPublicID(ctx, eventPublicID)
	if err != nil {
		return nil, err
	}
	if event == nil {
		return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
	}

	result := &EventDetailResult{}
	result.Event.ID = event.PublicID
	result.Event.Slug = event.Slug
	result.Event.DisplayName = event.DisplayName
	result.Event.Summary = event.Summary
	result.Event.Status = event.Status

	if event.CurrentReleasePubID != nil && event.ConfigLabel != nil && event.ManifestURL != nil {
		result.Release = &struct {
			ID                     string  `json:"id"`
			ConfigLabel            string  `json:"configLabel"`
			ManifestURL            string  `json:"manifestUrl"`
			ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
			RouteCode              *string `json:"routeCode,omitempty"`
		}{
			ID:                     *event.CurrentReleasePubID,
			ConfigLabel:            *event.ConfigLabel,
			ManifestURL:            *event.ManifestURL,
			ManifestChecksumSha256: event.ManifestChecksum,
			RouteCode:              event.RouteCode,
		}
	}
	result.ResolvedRelease = buildResolvedReleaseFromEvent(event, LaunchSourceEventCurrentRelease)
	result.Runtime = buildRuntimeSummaryFromEvent(event)
	result.CurrentPresentation = buildPresentationSummaryFromEvent(event)
	if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, event.PresentationID); err != nil {
		return nil, err
	} else if enrichedPresentation != nil {
		result.CurrentPresentation = enrichedPresentation
	}
	result.CurrentContentBundle = buildContentBundleSummaryFromEvent(event)
	if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, event.ContentBundleID); err != nil {
		return nil, err
	} else if enrichedBundle != nil {
		result.CurrentContentBundle = enrichedBundle
	}

	return result, nil
}

func (s *EventService) LaunchEvent(ctx context.Context, input LaunchEventInput) (*LaunchEventResult, error) {
	input.EventPublicID = strings.TrimSpace(input.EventPublicID)
	input.ReleaseID = strings.TrimSpace(input.ReleaseID)
	input.VariantID = strings.TrimSpace(input.VariantID)
	input.DeviceKey = strings.TrimSpace(input.DeviceKey)
	if err := validateClientType(input.ClientType); err != nil {
		return nil, err
	}
	if input.EventPublicID == "" || input.UserID == "" || input.DeviceKey == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "event id, user and deviceKey are required")
	}

	event, err := s.store.GetEventByPublicID(ctx, input.EventPublicID)
	if err != nil {
		return nil, err
	}
	if event == nil {
		return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
	}
	if event.Status != "active" {
		return nil, apperr.New(http.StatusConflict, "event_not_launchable", "event is not active")
	}
	if event.CurrentReleaseID == nil || event.CurrentReleasePubID == nil || event.ConfigLabel == nil || event.ManifestURL == nil {
		return nil, apperr.New(http.StatusConflict, "event_release_missing", "event does not have a published release")
	}
	if input.ReleaseID != "" && input.ReleaseID != *event.CurrentReleasePubID {
		return nil, apperr.New(http.StatusConflict, "release_not_launchable", "requested release is not the current published release")
	}
	variantPlan := resolveVariantPlan(event.ReleasePayloadJSON)
	variant, err := resolveLaunchVariant(variantPlan, input.VariantID)
	if err != nil {
		return nil, err
	}
	routeCode := event.RouteCode
	var assignmentMode *string
	var variantID *string
	var variantName *string
	if variant != nil {
		resultMode := variant.AssignmentMode
		assignmentMode = &resultMode
		variantID = &variant.ID
		variantName = &variant.Name
		if variant.RouteCode != nil {
			routeCode = variant.RouteCode
		}
	}

	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	sessionPublicID, err := security.GeneratePublicID("sess")
	if err != nil {
		return nil, err
	}
	sessionToken, err := security.GenerateToken(32)
	if err != nil {
		return nil, err
	}
	sessionTokenExpiresAt := time.Now().UTC().Add(2 * time.Hour)

	session, err := s.store.CreateGameSession(ctx, tx, postgres.CreateGameSessionParams{
		SessionPublicID:       sessionPublicID,
		UserID:                input.UserID,
		EventID:               event.ID,
		EventReleaseID:        *event.CurrentReleaseID,
		DeviceKey:             input.DeviceKey,
		ClientType:            input.ClientType,
		AssignmentMode:        assignmentMode,
		VariantID:             variantID,
		VariantName:           variantName,
		RouteCode:             routeCode,
		SessionTokenHash:      security.HashText(sessionToken),
		SessionTokenExpiresAt: sessionTokenExpiresAt,
	})
	if err != nil {
		return nil, err
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	result := &LaunchEventResult{}
	result.Event.ID = event.PublicID
	result.Event.DisplayName = event.DisplayName
	result.Launch.Source = LaunchSourceEventCurrentRelease
	result.Launch.ResolvedRelease = buildResolvedReleaseFromEvent(event, LaunchSourceEventCurrentRelease)
	result.Launch.Variant = variant
	result.Launch.Runtime = buildRuntimeSummaryFromEvent(event)
	result.Launch.Presentation = buildPresentationSummaryFromEvent(event)
	if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, event.PresentationID); err != nil {
		return nil, err
	} else if enrichedPresentation != nil {
		result.Launch.Presentation = enrichedPresentation
	}
	result.Launch.ContentBundle = buildContentBundleSummaryFromEvent(event)
	if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, event.ContentBundleID); err != nil {
		return nil, err
	} else if enrichedBundle != nil {
		result.Launch.ContentBundle = enrichedBundle
	}
	result.Launch.Config.ConfigURL = *event.ManifestURL
	result.Launch.Config.ConfigLabel = *event.ConfigLabel
	result.Launch.Config.ConfigChecksumSha256 = event.ManifestChecksum
	result.Launch.Config.ReleaseID = *event.CurrentReleasePubID
	result.Launch.Config.RouteCode = routeCode
	result.Launch.Business.Source = "direct-event"
	result.Launch.Business.EventID = event.PublicID
	result.Launch.Business.SessionID = session.SessionPublicID
	result.Launch.Business.SessionToken = sessionToken
	result.Launch.Business.SessionTokenExpiresAt = session.SessionTokenExpiresAt.Format(time.RFC3339)
	result.Launch.Business.RouteCode = routeCode
	return result, nil
}
