package ingest

import (
	"context"
	"fmt"
	"log/slog"
	"net/url"

	"github.com/gowvp/onvif"
	"github.com/gowvp/onvif/media"
	mediaSdk "github.com/gowvp/onvif/sdk/media"
	onvifXsd "github.com/gowvp/onvif/xsd/onvif"
)

type OnvifProfile struct {
	Name  onvifXsd.Name
	Token onvifXsd.ReferenceToken
	URI   *url.URL
}

// GetOnvifProfiles retrieves all available ONVIF stream profiles for a camera.
//
// Note that the result is very limited since many cameras provide extremely incomplete data,
// so it's safest to assume that we only have the bare minimum.
func GetOnvifProfiles(ctx context.Context, host, username, password string) ([]OnvifProfile, error) {
	dev, err := onvif.NewDevice(onvif.DeviceParams{
		Xaddr:    host,
		Username: username,
		Password: password,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create ONVIF device: %w", err)
	}

	// Get available profiles
	profiles, err := mediaSdk.Call_GetProfiles(ctx, dev, media.GetProfiles{})
	if err != nil {
		return nil, fmt.Errorf("failed to get ONVIF profiles: %w", err)
	}

	// Get available URIs for each profile
	onvifProfiles := make([]OnvifProfile, 0, len(profiles.Profiles))
	for _, p := range profiles.Profiles {
		uri, err := mediaSdk.Call_GetStreamUri(ctx, dev, media.GetStreamUri{
			StreamSetup: onvifXsd.StreamSetup{
				Stream: onvifXsd.StreamType("RTP-Unicast"),
				Transport: onvifXsd.Transport{
					Protocol: onvifXsd.TransportProtocol("TCP"),
				},
			},
			ProfileToken: p.Token,
		})
		if err != nil {
			return nil, fmt.Errorf("failed to get stream URI for profile %s: %w", p.Token, err)
		}
		if uri.MediaUri.Uri != "" {
			u, err := url.Parse(string(uri.MediaUri.Uri))
			if err != nil {
				return nil, fmt.Errorf("failed to parse stream URI for profile %s: %w", p.Token, err)
			}
			onvifProfiles = append(onvifProfiles, OnvifProfile{
				Name:  p.Name,
				Token: p.Token,
				URI:   u,
			})
		}
	}

	return onvifProfiles, nil
}

type StreamStats struct {
	FPS    float64
	Width  int
	Height int
}

// GetStreamStats starts a temporary pipeline to retrieve some basic (pre-decoding) information about the RTSP stream.
func GetStreamStats(ctx context.Context, log *slog.Logger, rtspUrl *url.URL, profileToken string) (stats StreamStats, err error) {
	p, err := CreatePipeline(log)
	if err != nil {
		return stats, fmt.Errorf("failed to create pipeline: %w", err)
	}
	if err := p.addRtspSource(rtspUrl); err != nil {
		return stats, fmt.Errorf("failed to add RTSP source: %w", err)
	}
	statsChan, err := p.addStatsBranch()
	if err != nil {
		return stats, fmt.Errorf("failed to add stats branch: %w", err)
	}

	statsFound := false
	runCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	go func() {
		select {
		case stats = <-statsChan:
			statsFound = true
			cancel()
		case <-runCtx.Done():
		}
	}()
	if err := p.Run(runCtx); err != nil {
		return stats, fmt.Errorf("failed to run pipeline: %w", err)
	}
	if !statsFound {
		return stats, fmt.Errorf("failed to retrieve stream stats: no stats received")
	}
	return stats, nil
}
