package streaming

import (
	"fmt"
	"mime"
	"path/filepath"
	"strconv"
	"strings"
)

const (
	// HLS segment duration in seconds
	DefaultSegmentDuration = 6.0
	// Target segment size in bytes (approximately)
	DefaultTargetSegmentSize = 2 * 1024 * 1024 // 2MB to match our chunk size
)

type HLSConfig struct {
	SegmentDuration     float64
	TargetSegmentSize   int64
	PlaylistType        string // VOD or LIVE
	AllowCache          bool
	Version             int
}

type MediaSegment struct {
	Index        int
	Duration     float64
	Size         int64
	ChunkIndexes []int // Which chunks make up this segment
	URI          string
}

type HLSPlaylist struct {
	Config         HLSConfig
	Segments       []MediaSegment
	TotalDuration  float64
	TargetDuration float64
	MediaSequence  int
	EndList        bool
}

// QualityLevel represents a transcoded quality level for HLS
type QualityLevel struct {
	Name       string
	Width      int
	Height     int
	Bitrate    string
	Bandwidth  int    // bits per second for HLS
	Codecs     string // codec string for HLS
	Resolution string // WxH format
	FileHash   string // hash of transcoded file
}

// MasterPlaylist represents an HLS master playlist with multiple quality levels
type MasterPlaylist struct {
	Qualities []QualityLevel
	BaseURL   string
}

// DefaultQualityLevels returns standard quality levels for HLS
func DefaultQualityLevels() []QualityLevel {
	return []QualityLevel{
		{
			Name:       "1080p",
			Width:      1920,
			Height:     1080,
			Bitrate:    "5000k",
			Bandwidth:  5000000,
			Codecs:     "avc1.640028,mp4a.40.2",
			Resolution: "1920x1080",
		},
		{
			Name:       "720p", 
			Width:      1280,
			Height:     720,
			Bitrate:    "2500k",
			Bandwidth:  2500000,
			Codecs:     "avc1.64001f,mp4a.40.2",
			Resolution: "1280x720",
		},
		{
			Name:       "480p",
			Width:      854,
			Height:     480,
			Bitrate:    "1000k",
			Bandwidth:  1000000,
			Codecs:     "avc1.64001e,mp4a.40.2",
			Resolution: "854x480",
		},
	}
}

type FileInfo struct {
	Name         string
	Size         int64
	ChunkCount   int
	ChunkSize    int
	Duration     float64 // For video files, estimated duration
	IsVideo      bool
	MimeType     string
}

// DefaultHLSConfig returns default HLS configuration
func DefaultHLSConfig() HLSConfig {
	return HLSConfig{
		SegmentDuration:   DefaultSegmentDuration,
		TargetSegmentSize: DefaultTargetSegmentSize,
		PlaylistType:      "VOD",
		AllowCache:        true,
		Version:           3,
	}
}

// DetectMediaType determines if a file is a video and its MIME type
func DetectMediaType(filename string) (bool, string) {
	ext := strings.ToLower(filepath.Ext(filename))
	mimeType := mime.TypeByExtension(ext)
	
	videoExtensions := map[string]bool{
		".mp4":  true,
		".mkv":  true,
		".avi":  true,
		".mov":  true,
		".wmv":  true,
		".flv":  true,
		".webm": true,
		".m4v":  true,
		".3gp":  true,
		".ts":   true,
	}
	
	isVideo := videoExtensions[ext]
	
	if mimeType == "" {
		if isVideo {
			// Default MIME type for unknown video extensions
			mimeType = "video/mp4"
		} else {
			mimeType = "application/octet-stream"
		}
	}
	
	return isVideo, mimeType
}

// EstimateVideoDuration provides a rough estimation of video duration based on file size
// This is a simple heuristic - in production you'd use ffprobe or similar
func EstimateVideoDuration(fileSize int64, filename string) float64 {
	// Very rough estimation: assume different bitrates based on file extension
	ext := strings.ToLower(filepath.Ext(filename))
	
	var estimatedBitrate int64 // bits per second
	
	switch ext {
	case ".mp4", ".m4v":
		estimatedBitrate = 2000000 // 2 Mbps average
	case ".mkv":
		estimatedBitrate = 3000000 // 3 Mbps average  
	case ".avi":
		estimatedBitrate = 1500000 // 1.5 Mbps average
	case ".webm":
		estimatedBitrate = 1000000 // 1 Mbps average
	default:
		estimatedBitrate = 2000000 // Default 2 Mbps
	}
	
	// Duration = (file size in bits) / bitrate
	fileSizeInBits := fileSize * 8
	duration := float64(fileSizeInBits) / float64(estimatedBitrate)
	
	// Ensure minimum duration of 10 seconds for very small files
	if duration < 10.0 {
		duration = 10.0
	}
	
	return duration
}

// GenerateHLSSegments creates HLS segments from file chunks
func GenerateHLSSegments(fileInfo FileInfo, config HLSConfig) (*HLSPlaylist, error) {
	if !fileInfo.IsVideo {
		return nil, fmt.Errorf("file is not a video: %s", fileInfo.Name)
	}
	
	playlist := &HLSPlaylist{
		Config:        config,
		Segments:      make([]MediaSegment, 0),
		MediaSequence: 0,
		EndList:       true, // VOD content
	}
	
	// Calculate number of segments based on duration and target segment duration
	totalSegments := int(fileInfo.Duration/config.SegmentDuration) + 1
	if totalSegments < 1 {
		totalSegments = 1
	}
	
	segmentDuration := fileInfo.Duration / float64(totalSegments)
	playlist.TargetDuration = segmentDuration
	
	// Calculate chunks per segment
	chunksPerSegment := fileInfo.ChunkCount / totalSegments
	if chunksPerSegment < 1 {
		chunksPerSegment = 1
	}
	
	// Generate segments
	for i := 0; i < totalSegments; i++ {
		startChunk := i * chunksPerSegment
		endChunk := startChunk + chunksPerSegment
		
		// Handle last segment
		if i == totalSegments-1 {
			endChunk = fileInfo.ChunkCount
		}
		
		// Ensure we don't exceed chunk count
		if endChunk > fileInfo.ChunkCount {
			endChunk = fileInfo.ChunkCount
		}
		
		chunkIndexes := make([]int, 0)
		for j := startChunk; j < endChunk; j++ {
			chunkIndexes = append(chunkIndexes, j)
		}
		
		segmentSize := int64(len(chunkIndexes)) * int64(fileInfo.ChunkSize)
		
		segment := MediaSegment{
			Index:        i,
			Duration:     segmentDuration,
			Size:         segmentSize,
			ChunkIndexes: chunkIndexes,
			URI:          fmt.Sprintf("segment_%d.ts", i),
		}
		
		playlist.Segments = append(playlist.Segments, segment)
	}
	
	playlist.TotalDuration = fileInfo.Duration
	
	return playlist, nil
}

// GenerateM3U8Manifest creates the HLS playlist manifest
func (p *HLSPlaylist) GenerateM3U8Manifest(baseURL string) string {
	var builder strings.Builder
	
	// Header
	builder.WriteString("#EXTM3U\n")
	builder.WriteString(fmt.Sprintf("#EXT-X-VERSION:%d\n", p.Config.Version))
	builder.WriteString(fmt.Sprintf("#EXT-X-TARGETDURATION:%d\n", int(p.TargetDuration)+1))
	builder.WriteString(fmt.Sprintf("#EXT-X-MEDIA-SEQUENCE:%d\n", p.MediaSequence))
	builder.WriteString(fmt.Sprintf("#EXT-X-PLAYLIST-TYPE:%s\n", p.Config.PlaylistType))
	
	if !p.Config.AllowCache {
		builder.WriteString("#EXT-X-ALLOW-CACHE:NO\n")
	}
	
	// Segments
	for _, segment := range p.Segments {
		builder.WriteString(fmt.Sprintf("#EXTINF:%.3f,\n", segment.Duration))
		segmentURL := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), segment.URI)
		builder.WriteString(segmentURL + "\n")
	}
	
	// End marker for VOD
	if p.EndList {
		builder.WriteString("#EXT-X-ENDLIST\n")
	}
	
	return builder.String()
}

// GetSegmentByIndex returns a segment by its index
func (p *HLSPlaylist) GetSegmentByIndex(index int) (*MediaSegment, error) {
	if index < 0 || index >= len(p.Segments) {
		return nil, fmt.Errorf("segment index %d out of range (0-%d)", index, len(p.Segments)-1)
	}
	
	return &p.Segments[index], nil
}

// GetSegmentByURI returns a segment by its URI
func (p *HLSPlaylist) GetSegmentByURI(uri string) (*MediaSegment, error) {
	for _, segment := range p.Segments {
		if segment.URI == uri {
			return &segment, nil
		}
	}
	
	return nil, fmt.Errorf("segment not found: %s", uri)
}

// ParseSegmentURI extracts segment index from URI like "segment_0.ts"
func ParseSegmentURI(uri string) (int, error) {
	// Remove extension
	name := strings.TrimSuffix(uri, filepath.Ext(uri))
	
	// Extract number from "segment_N" format
	parts := strings.Split(name, "_")
	if len(parts) != 2 || parts[0] != "segment" {
		return 0, fmt.Errorf("invalid segment URI format: %s", uri)
	}
	
	index, err := strconv.Atoi(parts[1])
	if err != nil {
		return 0, fmt.Errorf("invalid segment index in URI %s: %v", uri, err)
	}
	
	return index, nil
}

// RangeRequest represents an HTTP range request
type RangeRequest struct {
	Start int64
	End   int64
	Size  int64
}

// ParseRangeHeader parses HTTP Range header like "bytes=0-1023"
func ParseRangeHeader(rangeHeader string, fileSize int64) (*RangeRequest, error) {
	if rangeHeader == "" {
		return nil, nil
	}
	
	// Remove "bytes=" prefix
	if !strings.HasPrefix(rangeHeader, "bytes=") {
		return nil, fmt.Errorf("invalid range header format: %s", rangeHeader)
	}
	
	rangeSpec := strings.TrimPrefix(rangeHeader, "bytes=")
	
	// Handle different range formats
	if strings.Contains(rangeSpec, ",") {
		// Multiple ranges not supported for simplicity
		return nil, fmt.Errorf("multiple ranges not supported")
	}
	
	parts := strings.Split(rangeSpec, "-")
	if len(parts) != 2 {
		return nil, fmt.Errorf("invalid range format: %s", rangeSpec)
	}
	
	var start, end int64
	var err error
	
	// Parse start
	if parts[0] != "" {
		start, err = strconv.ParseInt(parts[0], 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid start range: %v", err)
		}
	}
	
	// Parse end
	if parts[1] != "" {
		end, err = strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid end range: %v", err)
		}
	} else {
		// If no end specified, use file size - 1
		end = fileSize - 1
	}
	
	// Handle suffix-byte-range-spec (e.g., "-500" means last 500 bytes)
	if parts[0] == "" {
		start = fileSize - end
		end = fileSize - 1
	}
	
	// Validate range
	if start < 0 {
		start = 0
	}
	if end >= fileSize {
		end = fileSize - 1
	}
	if start > end {
		return nil, fmt.Errorf("invalid range: start %d > end %d", start, end)
	}
	
	return &RangeRequest{
		Start: start,
		End:   end,
		Size:  end - start + 1,
	}, nil
}

// FormatContentRange formats the Content-Range header
func (r *RangeRequest) FormatContentRange(fileSize int64) string {
	return fmt.Sprintf("bytes %d-%d/%d", r.Start, r.End, fileSize)
}

// ChunkRange represents which chunks and byte offsets are needed for a range request
type ChunkRange struct {
	StartChunk  int
	EndChunk    int
	StartOffset int64 // Byte offset within start chunk
	EndOffset   int64 // Byte offset within end chunk
	TotalBytes  int64
}

// CalculateChunkRange determines which chunks are needed for a byte range
func CalculateChunkRange(rangeReq *RangeRequest, chunkSize int) *ChunkRange {
	startChunk := int(rangeReq.Start / int64(chunkSize))
	endChunk := int(rangeReq.End / int64(chunkSize))
	
	startOffset := rangeReq.Start % int64(chunkSize)
	endOffset := rangeReq.End % int64(chunkSize)
	
	return &ChunkRange{
		StartChunk:  startChunk,
		EndChunk:    endChunk,
		StartOffset: startOffset,
		EndOffset:   endOffset,
		TotalBytes:  rangeReq.Size,
	}
}

// GenerateMasterPlaylist creates an HLS master playlist for adaptive bitrate streaming
func (mp *MasterPlaylist) GenerateMasterPlaylist() string {
	var builder strings.Builder
	
	// Header
	builder.WriteString("#EXTM3U\n")
	builder.WriteString("#EXT-X-VERSION:6\n")
	
	// Stream information for each quality
	for _, quality := range mp.Qualities {
		// EXT-X-STREAM-INF line with bandwidth, resolution, and codecs
		builder.WriteString(fmt.Sprintf("#EXT-X-STREAM-INF:BANDWIDTH=%d,RESOLUTION=%s,CODECS=\"%s\"\n",
			quality.Bandwidth, quality.Resolution, quality.Codecs))
		
		// Playlist URL for this quality
		playlistURL := fmt.Sprintf("%s/%s.m3u8", strings.TrimSuffix(mp.BaseURL, "/"), quality.Name)
		builder.WriteString(playlistURL + "\n")
	}
	
	return builder.String()
}

// CreateHLSForQuality generates HLS segments and playlist for a specific quality
func CreateHLSForQuality(fileInfo FileInfo, config HLSConfig, qualityLevel QualityLevel, baseURL string) (*HLSPlaylist, error) {
	if !fileInfo.IsVideo {
		return nil, fmt.Errorf("file is not a video: %s", fileInfo.Name)
	}
	
	// Update file info with quality-specific hash if available
	if qualityLevel.FileHash != "" {
		fileInfo.Name = fmt.Sprintf("%s_%s", fileInfo.Name, qualityLevel.Name)
	}
	
	// Generate standard HLS playlist
	playlist, err := GenerateHLSSegments(fileInfo, config)
	if err != nil {
		return nil, err
	}
	
	// Update segment URIs to include quality prefix
	for i := range playlist.Segments {
		playlist.Segments[i].URI = fmt.Sprintf("%s_segment_%d.ts", qualityLevel.Name, playlist.Segments[i].Index)
	}
	
	return playlist, nil
}

// GenerateMultiQualityHLS creates HLS playlists for multiple quality levels
func GenerateMultiQualityHLS(fileInfo FileInfo, config HLSConfig, qualityLevels []QualityLevel, baseURL string) (*MasterPlaylist, map[string]*HLSPlaylist, error) {
	if !fileInfo.IsVideo {
		return nil, nil, fmt.Errorf("file is not a video: %s", fileInfo.Name)
	}
	
	masterPlaylist := &MasterPlaylist{
		Qualities: qualityLevels,
		BaseURL:   baseURL,
	}
	
	qualityPlaylists := make(map[string]*HLSPlaylist)
	
	// Generate playlist for each quality
	for _, quality := range qualityLevels {
		playlist, err := CreateHLSForQuality(fileInfo, config, quality, baseURL)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to create HLS for quality %s: %w", quality.Name, err)
		}
		qualityPlaylists[quality.Name] = playlist
	}
	
	return masterPlaylist, qualityPlaylists, nil
}