package server

import (
	"encoding/xml"
	"fmt"
)

// Media service SOAP message types

// GetProfilesResponse represents GetProfiles response.
type GetProfilesResponse struct {
	XMLName  xml.Name       `xml:"http://www.onvif.org/ver10/media/wsdl GetProfilesResponse"`
	Profiles []MediaProfile `xml:"Profiles"`
}

// MediaProfile represents a media profile.
type MediaProfile struct {
	Token                       string                       `xml:"token,attr"`
	Fixed                       bool                         `xml:"fixed,attr"`
	Name                        string                       `xml:"Name"`
	VideoSourceConfiguration    *VideoSourceConfiguration    `xml:"VideoSourceConfiguration"`
	AudioSourceConfiguration    *AudioSourceConfiguration    `xml:"AudioSourceConfiguration,omitempty"`
	VideoEncoderConfiguration   *VideoEncoderConfiguration   `xml:"VideoEncoderConfiguration"`
	AudioEncoderConfiguration   *AudioEncoderConfiguration   `xml:"AudioEncoderConfiguration,omitempty"`
	VideoAnalyticsConfiguration *VideoAnalyticsConfiguration `xml:"VideoAnalyticsConfiguration,omitempty"`
	PTZConfiguration            *PTZConfiguration            `xml:"PTZConfiguration,omitempty"`
	MetadataConfiguration       *MetadataConfiguration       `xml:"MetadataConfiguration,omitempty"`
}

// VideoSourceConfiguration represents video source configuration.
type VideoSourceConfiguration struct {
	Token       string       `xml:"token,attr"`
	Name        string       `xml:"Name"`
	UseCount    int          `xml:"UseCount"`
	SourceToken string       `xml:"SourceToken"`
	Bounds      IntRectangle `xml:"Bounds"`
}

// AudioSourceConfiguration represents audio source configuration.
type AudioSourceConfiguration struct {
	Token       string `xml:"token,attr"`
	Name        string `xml:"Name"`
	UseCount    int    `xml:"UseCount"`
	SourceToken string `xml:"SourceToken"`
}

// VideoEncoderConfiguration represents video encoder configuration.
type VideoEncoderConfiguration struct {
	Token          string                  `xml:"token,attr"`
	Name           string                  `xml:"Name"`
	UseCount       int                     `xml:"UseCount"`
	Encoding       string                  `xml:"Encoding"`
	Resolution     VideoResolution         `xml:"Resolution"`
	Quality        float64                 `xml:"Quality"`
	RateControl    *VideoRateControl       `xml:"RateControl,omitempty"`
	H264           *H264Configuration      `xml:"H264,omitempty"`
	Multicast      *MulticastConfiguration `xml:"Multicast,omitempty"`
	SessionTimeout string                  `xml:"SessionTimeout"`
}

// AudioEncoderConfiguration represents audio encoder configuration.
type AudioEncoderConfiguration struct {
	Token          string                  `xml:"token,attr"`
	Name           string                  `xml:"Name"`
	UseCount       int                     `xml:"UseCount"`
	Encoding       string                  `xml:"Encoding"`
	Bitrate        int                     `xml:"Bitrate"`
	SampleRate     int                     `xml:"SampleRate"`
	Multicast      *MulticastConfiguration `xml:"Multicast,omitempty"`
	SessionTimeout string                  `xml:"SessionTimeout"`
}

// VideoAnalyticsConfiguration represents video analytics configuration.
type VideoAnalyticsConfiguration struct {
	Token    string `xml:"token,attr"`
	Name     string `xml:"Name"`
	UseCount int    `xml:"UseCount"`
}

// PTZConfiguration represents PTZ configuration.
type PTZConfiguration struct {
	Token     string `xml:"token,attr"`
	Name      string `xml:"Name"`
	UseCount  int    `xml:"UseCount"`
	NodeToken string `xml:"NodeToken"`
}

// MetadataConfiguration represents metadata configuration.
type MetadataConfiguration struct {
	Token          string `xml:"token,attr"`
	Name           string `xml:"Name"`
	UseCount       int    `xml:"UseCount"`
	SessionTimeout string `xml:"SessionTimeout"`
}

// IntRectangle represents a rectangle with integer coordinates.
type IntRectangle struct {
	X      int `xml:"x,attr"`
	Y      int `xml:"y,attr"`
	Width  int `xml:"width,attr"`
	Height int `xml:"height,attr"`
}

// VideoResolution represents video resolution.
type VideoResolution struct {
	Width  int `xml:"Width"`
	Height int `xml:"Height"`
}

// VideoRateControl represents video rate control.
type VideoRateControl struct {
	FrameRateLimit   int `xml:"FrameRateLimit"`
	EncodingInterval int `xml:"EncodingInterval"`
	BitrateLimit     int `xml:"BitrateLimit"`
}

// H264Configuration represents H264 configuration.
type H264Configuration struct {
	GovLength   int    `xml:"GovLength"`
	H264Profile string `xml:"H264Profile"`
}

// MulticastConfiguration represents multicast configuration.
type MulticastConfiguration struct {
	Address   IPAddress `xml:"Address"`
	Port      int       `xml:"Port"`
	TTL       int       `xml:"TTL"`
	AutoStart bool      `xml:"AutoStart"`
}

// IPAddress represents an IP address.
type IPAddress struct {
	Type        string `xml:"Type"`
	IPv4Address string `xml:"IPv4Address,omitempty"`
	IPv6Address string `xml:"IPv6Address,omitempty"`
}

// GetStreamURIResponse represents GetStreamURI response.
type GetStreamURIResponse struct {
	XMLName  xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetStreamURIResponse"`
	MediaURI MediaURI `xml:"MediaUri"`
}

// MediaURI represents a media URI.
type MediaURI struct {
	URI                 string `xml:"Uri"`
	InvalidAfterConnect bool   `xml:"InvalidAfterConnect"`
	InvalidAfterReboot  bool   `xml:"InvalidAfterReboot"`
	Timeout             string `xml:"Timeout"`
}

// GetSnapshotURIResponse represents GetSnapshotURI response.
type GetSnapshotURIResponse struct {
	XMLName  xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetSnapshotURIResponse"`
	MediaURI MediaURI `xml:"MediaUri"`
}

// GetVideoSourcesResponse represents GetVideoSources response.
type GetVideoSourcesResponse struct {
	XMLName      xml.Name      `xml:"http://www.onvif.org/ver10/media/wsdl GetVideoSourcesResponse"`
	VideoSources []VideoSource `xml:"VideoSources"`
}

// VideoSource represents a video source.
type VideoSource struct {
	Token      string          `xml:"token,attr"`
	Framerate  float64         `xml:"Framerate"`
	Resolution VideoResolution `xml:"Resolution"`
}

// Media service handlers

// HandleGetProfiles handles GetProfiles request.
func (s *Server) HandleGetProfiles(body interface{}) (interface{}, error) {
	profiles := make([]MediaProfile, len(s.config.Profiles))

	//nolint:gocritic // Range value copy is acceptable for small structs
	for i, profileCfg := range s.config.Profiles {
		profile := MediaProfile{
			Token: profileCfg.Token,
			Fixed: true,
			Name:  profileCfg.Name,
			VideoSourceConfiguration: &VideoSourceConfiguration{
				Token:       profileCfg.VideoSource.Token,
				Name:        profileCfg.VideoSource.Name,
				UseCount:    1,
				SourceToken: profileCfg.VideoSource.Token,
				Bounds: IntRectangle{
					X:      profileCfg.VideoSource.Bounds.X,
					Y:      profileCfg.VideoSource.Bounds.Y,
					Width:  profileCfg.VideoSource.Bounds.Width,
					Height: profileCfg.VideoSource.Bounds.Height,
				},
			},
			VideoEncoderConfiguration: &VideoEncoderConfiguration{
				Token:    profileCfg.Token + "_encoder",
				Name:     profileCfg.Name + " Encoder",
				UseCount: 1,
				Encoding: profileCfg.VideoEncoder.Encoding,
				Resolution: VideoResolution{
					Width:  profileCfg.VideoEncoder.Resolution.Width,
					Height: profileCfg.VideoEncoder.Resolution.Height,
				},
				Quality: profileCfg.VideoEncoder.Quality,
				RateControl: &VideoRateControl{
					FrameRateLimit:   profileCfg.VideoEncoder.Framerate,
					EncodingInterval: 1,
					BitrateLimit:     profileCfg.VideoEncoder.Bitrate,
				},
				SessionTimeout: "PT60S",
			},
		}

		// Add H264 configuration if encoding is H264
		if profileCfg.VideoEncoder.Encoding == "H264" {
			profile.VideoEncoderConfiguration.H264 = &H264Configuration{
				GovLength:   profileCfg.VideoEncoder.GovLength,
				H264Profile: "Main",
			}
		}

		// Add audio configuration if present
		if profileCfg.AudioSource != nil {
			profile.AudioSourceConfiguration = &AudioSourceConfiguration{
				Token:       profileCfg.AudioSource.Token,
				Name:        profileCfg.AudioSource.Name,
				UseCount:    1,
				SourceToken: profileCfg.AudioSource.Token,
			}
		}

		if profileCfg.AudioEncoder != nil {
			profile.AudioEncoderConfiguration = &AudioEncoderConfiguration{
				Token:          profileCfg.Token + "_audio_encoder",
				Name:           profileCfg.Name + " Audio Encoder",
				UseCount:       1,
				Encoding:       profileCfg.AudioEncoder.Encoding,
				Bitrate:        profileCfg.AudioEncoder.Bitrate,
				SampleRate:     profileCfg.AudioEncoder.SampleRate,
				SessionTimeout: "PT60S",
			}
		}

		// Add PTZ configuration if present
		if profileCfg.PTZ != nil {
			profile.PTZConfiguration = &PTZConfiguration{
				Token:     profileCfg.PTZ.NodeToken,
				Name:      profileCfg.Name + " PTZ",
				UseCount:  1,
				NodeToken: profileCfg.PTZ.NodeToken,
			}
		}

		profiles[i] = profile
	}

	return &GetProfilesResponse{
		Profiles: profiles,
	}, nil
}

// HandleGetStreamURI handles GetStreamURI request.
func (s *Server) HandleGetStreamURI(body interface{}) (interface{}, error) {
	var req struct {
		ProfileToken string `xml:"ProfileToken"`
	}

	if err := unmarshalBody(body, &req); err != nil {
		return nil, fmt.Errorf("invalid request: %w", err)
	}

	// Find the stream configuration for this profile
	streamCfg, ok := s.streams[req.ProfileToken]
	if !ok {
		return nil, fmt.Errorf("%w: %s", ErrProfileNotFound, req.ProfileToken)
	}

	// Build RTSP URI
	uri := streamCfg.StreamURI
	if uri == "" {
		// Default URI construction
		host := s.config.Host
		if host == defaultHost || host == "" {
			host = defaultHostname
		}
		uri = fmt.Sprintf("rtsp://%s:8554%s", host, streamCfg.RTSPPath)
	}

	return &GetStreamURIResponse{
		MediaURI: MediaURI{
			URI:                 uri,
			InvalidAfterConnect: false,
			InvalidAfterReboot:  true,
			Timeout:             "PT60S",
		},
	}, nil
}

// HandleGetSnapshotURI handles GetSnapshotURI request.
func (s *Server) HandleGetSnapshotURI(body interface{}) (interface{}, error) {
	var req struct {
		ProfileToken string `xml:"ProfileToken"`
	}

	if err := unmarshalBody(body, &req); err != nil {
		return nil, fmt.Errorf("invalid request: %w", err)
	}

	// Find the profile
	var profileCfg *ProfileConfig
	for i := range s.config.Profiles {
		if s.config.Profiles[i].Token == req.ProfileToken {
			profileCfg = &s.config.Profiles[i]

			break
		}
	}

	if profileCfg == nil {
		return nil, fmt.Errorf("%w: %s", ErrProfileNotFound, req.ProfileToken)
	}

	if !profileCfg.Snapshot.Enabled {
		return nil, fmt.Errorf("%w: %s", ErrSnapshotNotSupported, req.ProfileToken)
	}

	// Build snapshot URI
	host := s.config.Host
	if host == defaultHost || host == "" {
		host = defaultHostname
	}
	uri := fmt.Sprintf("http://%s:%d%s/snapshot?profile=%s",
		host, s.config.Port, s.config.BasePath, req.ProfileToken)

	return &GetSnapshotURIResponse{
		MediaURI: MediaURI{
			URI:                 uri,
			InvalidAfterConnect: false,
			InvalidAfterReboot:  true,
			Timeout:             "PT5S",
		},
	}, nil
}

// HandleGetVideoSources handles GetVideoSources request.
func (s *Server) HandleGetVideoSources(body interface{}) (interface{}, error) {
	sources := make([]VideoSource, 0)

	// Collect unique video sources from profiles
	seenSources := make(map[string]bool)
	//nolint:gocritic // Range value copy is acceptable for small structs
	for _, profileCfg := range s.config.Profiles {
		if !seenSources[profileCfg.VideoSource.Token] {
			sources = append(sources, VideoSource{
				Token:     profileCfg.VideoSource.Token,
				Framerate: float64(profileCfg.VideoSource.Framerate),
				Resolution: VideoResolution{
					Width:  profileCfg.VideoSource.Resolution.Width,
					Height: profileCfg.VideoSource.Resolution.Height,
				},
			})
			seenSources[profileCfg.VideoSource.Token] = true
		}
	}

	return &GetVideoSourcesResponse{
		VideoSources: sources,
	}, nil
}

// unmarshalBody is a helper to unmarshal SOAP body content.
func unmarshalBody(body, target interface{}) error {
	var bodyXML []byte
	var err error

	// If body is already []byte, use it directly
	if b, ok := body.([]byte); ok {
		bodyXML = b
	} else {
		bodyXML, err = xml.Marshal(body)
		if err != nil {
			return fmt.Errorf("failed to marshal XML: %w", err)
		}
	}

	if err := xml.Unmarshal(bodyXML, target); err != nil {
		return fmt.Errorf("failed to unmarshal XML: %w", err)
	}

	return nil
}
