package stream

import (
	"encoding/base64"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"github.com/eduard256/Strix/internal/models"
)

// Builder handles stream URL construction
type Builder struct {
	queryParams []string
	logger      interface{ Debug(string, ...any) }
}

// NewBuilder creates a new stream URL builder
func NewBuilder(queryParams []string, logger interface{ Debug(string, ...any) }) *Builder {
	return &Builder{
		queryParams: queryParams,
		logger:      logger,
	}
}

// BuildContext contains parameters for URL building
type BuildContext struct {
	IP       string
	Port     int
	Username string
	Password string
	Channel  int
	Width    int
	Height   int
	Protocol string
	Path     string
}

// BuildURL builds a complete URL from an entry and context
func (b *Builder) BuildURL(entry models.CameraEntry, ctx BuildContext) string {
	b.logger.Debug("BuildURL called",
		"entry_type", entry.Type,
		"entry_url", entry.URL,
		"entry_port", entry.Port,
		"entry_protocol", entry.Protocol,
		"ctx_ip", ctx.IP,
		"ctx_port", ctx.Port,
		"ctx_username", ctx.Username,
		"ctx_channel", ctx.Channel)

	// Set defaults
	if ctx.Width == 0 {
		ctx.Width = 640
	}
	if ctx.Height == 0 {
		ctx.Height = 480
	}
	// NOTE: Channel default is 0 - will only be used for [CHANNEL] placeholder replacement
	// Literal channel values in URLs (like "channel=1") are preserved as-is

	// Use entry's port if not specified
	if ctx.Port == 0 {
		ctx.Port = entry.Port

		// If entry port is also 0, use default port for the protocol
		if ctx.Port == 0 {
			// Use entry's protocol if not specified for port determination
			protocol := ctx.Protocol
			if protocol == "" {
				protocol = entry.Protocol
			}

			switch protocol {
			case "http":
				ctx.Port = 80
			case "https":
				ctx.Port = 443
			case "rtsp", "rtsps":
				ctx.Port = 554
			default:
				ctx.Port = 80 // Default to 80 if unknown
			}

			b.logger.Debug("using default port for protocol",
				"protocol", protocol,
				"default_port", ctx.Port)
		}
	}

	// Use entry's protocol if not specified
	if ctx.Protocol == "" {
		ctx.Protocol = entry.Protocol
	}

	// Replace placeholders in URL path
	path := b.replacePlaceholders(entry.URL, ctx)
	b.logger.Debug("placeholders replaced", "original", entry.URL, "after_replacement", path)

	// Build the complete URL
	var fullURL string

	// Check if the URL already contains authentication parameters
	hasAuthInURL := b.hasAuthenticationParams(path)
	b.logger.Debug("auth params detection", "has_auth_in_url", hasAuthInURL, "path", path)

	switch ctx.Protocol {
	case "rtsp":
		if ctx.Username != "" && ctx.Password != "" && !hasAuthInURL {
			// Standard ports can be omitted
			if ctx.Port == 554 {
				fullURL = fmt.Sprintf("rtsp://%s:%s@%s/%s",
					ctx.Username, ctx.Password, ctx.IP, path)
			} else {
				fullURL = fmt.Sprintf("rtsp://%s:%s@%s:%d/%s",
					ctx.Username, ctx.Password, ctx.IP, ctx.Port, path)
			}
		} else {
			if ctx.Port == 554 {
				fullURL = fmt.Sprintf("rtsp://%s/%s", ctx.IP, path)
			} else {
				fullURL = fmt.Sprintf("rtsp://%s:%d/%s", ctx.IP, ctx.Port, path)
			}
		}

	case "http", "https":
		// For HTTP, check if auth should be in URL or parameters
		if ctx.Username != "" && ctx.Password != "" && !hasAuthInURL {
			// Don't put auth in URL for HTTP, will use Basic Auth header
			if (ctx.Protocol == "http" && ctx.Port == 80) ||
			   (ctx.Protocol == "https" && ctx.Port == 443) {
				fullURL = fmt.Sprintf("%s://%s/%s", ctx.Protocol, ctx.IP, path)
			} else {
				fullURL = fmt.Sprintf("%s://%s:%d/%s", ctx.Protocol, ctx.IP, ctx.Port, path)
			}
		} else {
			if (ctx.Protocol == "http" && ctx.Port == 80) ||
			   (ctx.Protocol == "https" && ctx.Port == 443) {
				fullURL = fmt.Sprintf("%s://%s/%s", ctx.Protocol, ctx.IP, path)
			} else {
				fullURL = fmt.Sprintf("%s://%s:%d/%s", ctx.Protocol, ctx.IP, ctx.Port, path)
			}
		}

	default:
		// Generic URL construction
		fullURL = fmt.Sprintf("%s://%s:%d/%s", ctx.Protocol, ctx.IP, ctx.Port, path)
	}

	// Clean up double slashes (except after protocol://)
	fullURL = b.cleanURL(fullURL)

	b.logger.Debug("BuildURL complete",
		"final_url", fullURL,
		"entry_type", entry.Type,
		"entry_url_pattern", entry.URL,
		"protocol", ctx.Protocol,
		"port", ctx.Port,
		"has_auth_in_url", hasAuthInURL)

	return fullURL
}

// replacePlaceholders replaces all placeholders in the URL
func (b *Builder) replacePlaceholders(urlPath string, ctx BuildContext) string {
	result := urlPath

	// Generate base64 auth for [AUTH] placeholder
	auth := ""
	if ctx.Username != "" && ctx.Password != "" {
		auth = base64.StdEncoding.EncodeToString([]byte(ctx.Username + ":" + ctx.Password))
	}

	// Common placeholders
	replacements := map[string]string{
		"[CHANNEL]":   strconv.Itoa(ctx.Channel),
		"[channel]":   strconv.Itoa(ctx.Channel),
		"[CHANNEL+1]": strconv.Itoa(ctx.Channel + 1), // For Hikvision-style channels (101, 201, 301...)
		"[channel+1]": strconv.Itoa(ctx.Channel + 1),
		"{CHANNEL}":   strconv.Itoa(ctx.Channel),     // BUBBLE protocol uses {channel}
		"{channel}":   strconv.Itoa(ctx.Channel),
		"{CHANNEL+1}": strconv.Itoa(ctx.Channel + 1),
		"{channel+1}": strconv.Itoa(ctx.Channel + 1),
		"[WIDTH]":     strconv.Itoa(ctx.Width),
		"[width]":     strconv.Itoa(ctx.Width),
		"[HEIGHT]":    strconv.Itoa(ctx.Height),
		"[height]":    strconv.Itoa(ctx.Height),
		"[USERNAME]":  ctx.Username,
		"[username]":  ctx.Username,
		"[PASSWORD]":  ctx.Password,
		"[password]":  ctx.Password,
		"[PASWORD]":   ctx.Password, // Handle typo in database
		"[pasword]":   ctx.Password,
		"[USER]":      ctx.Username,
		"[user]":      ctx.Username,
		"[PASS]":      ctx.Password,
		"[pass]":      ctx.Password,
		"[PWD]":       ctx.Password,
		"[pwd]":       ctx.Password,
		"[IP]":        ctx.IP,
		"[ip]":        ctx.IP,
		"[PORT]":      strconv.Itoa(ctx.Port),
		"[port]":      strconv.Itoa(ctx.Port),
		"[AUTH]":      auth, // base64(username:password) for basic auth
		"[auth]":      auth,
		"[TOKEN]":     "", // Empty for now
		"[token]":     "",
	}

	// Replace all placeholders
	for placeholder, value := range replacements {
		result = strings.ReplaceAll(result, placeholder, value)
	}

	// Handle query parameter placeholders (only for auth params)
	result = b.replaceQueryParams(result, ctx)

	return result
}


// replaceQueryParams handles query parameter replacements
func (b *Builder) replaceQueryParams(urlPath string, ctx BuildContext) string {
	// Parse URL to handle query params
	parts := strings.SplitN(urlPath, "?", 2)
	if len(parts) < 2 {
		return urlPath
	}

	basePath := parts[0]
	queryString := parts[1]

	// Parse query parameters
	params, err := url.ParseQuery(queryString)
	if err != nil {
		return urlPath
	}

	// ONLY replace authentication parameters
	// DO NOT replace channel, width, height - they should stay as-is from URL patterns
	for key := range params {
		lowerKey := strings.ToLower(key)

		switch lowerKey {
		case "user", "username", "usr", "loginuse":
			params.Set(key, ctx.Username)
		case "password", "pass", "pwd", "loginpas", "passwd":
			params.Set(key, ctx.Password)
		// Removed: channel, width, height replacements - they were breaking working URLs
		}
	}

	// Rebuild URL
	return basePath + "?" + params.Encode()
}


// hasAuthenticationParams checks if URL contains auth parameters
func (b *Builder) hasAuthenticationParams(urlPath string) bool {
	authParams := []string{
		"user=", "username=", "usr=", "loginuse=",
		"password=", "pass=", "pwd=", "loginpas=", "passwd=",
	}

	lowerPath := strings.ToLower(urlPath)
	for _, param := range authParams {
		if strings.Contains(lowerPath, param) {
			return true
		}
	}

	return false
}

// cleanURL cleans up the URL
func (b *Builder) cleanURL(fullURL string) string {
	// Remove double slashes except after protocol://
	protocolEnd := strings.Index(fullURL, "://")
	if protocolEnd > 0 {
		protocol := fullURL[:protocolEnd+3]
		rest := fullURL[protocolEnd+3:]

		// Replace multiple slashes with single slash
		rest = regexp.MustCompile(`/{2,}`).ReplaceAllString(rest, "/")

		return protocol + rest
	}

	return fullURL
}

// BuildURLsFromEntry generates all possible URLs from a camera entry
func (b *Builder) BuildURLsFromEntry(entry models.CameraEntry, ctx BuildContext) []string {
	urlMap := make(map[string]bool)
	var urls []string

	// Helper to add unique URLs
	addURL := func(url string) {
		if !urlMap[url] {
			urls = append(urls, url)
			urlMap[url] = true
		}
	}

	switch entry.Protocol {
	case "bubble":
		// BUBBLE protocol: proprietary Chinese NVR/DVR protocol
		// Always use HTTP with embedded credentials
		if ctx.Username != "" && ctx.Password != "" {
			// Build HTTP URL with credentials embedded
			ctxHTTP := ctx
			ctxHTTP.Protocol = "http"

			baseURL := b.BuildURL(entry, ctxHTTP)

			// Parse and add credentials to URL
			if u, err := url.Parse(baseURL); err == nil {
				u.User = url.UserPassword(ctx.Username, ctx.Password)
				addURL(u.String())
			}
		} else {
			// No credentials - try anyway (some cameras might work)
			ctxHTTP := ctx
			ctxHTTP.Protocol = "http"
			addURL(b.BuildURL(entry, ctxHTTP))
		}

	case "rtsp", "rtsps":
		// For RTSP: generate ONLY with credentials if provided, otherwise without
		if ctx.Username != "" && ctx.Password != "" {
			// Credentials provided - generate ONLY URL with auth
			addURL(b.BuildURL(entry, ctx))
		} else {
			// No credentials - generate ONLY URL without auth
			ctxNoAuth := ctx
			ctxNoAuth.Username = ""
			ctxNoAuth.Password = ""
			addURL(b.BuildURL(entry, ctxNoAuth))
		}

	case "http", "https":
		// For HTTP/HTTPS: ALWAYS generate 4 authentication variants
		if ctx.Username != "" && ctx.Password != "" {
			// 1. No authentication
			ctxNoAuth := ctx
			ctxNoAuth.Username = ""
			ctxNoAuth.Password = ""
			urlNoAuth := b.BuildURL(entry, ctxNoAuth)
			addURL(urlNoAuth)

			// 2. Basic Auth only (embedded credentials)
			urlBasic := b.BuildURL(entry, ctxNoAuth) // Use clean URL
			if u, err := url.Parse(urlBasic); err == nil {
				u.User = url.UserPassword(ctx.Username, ctx.Password)
				addURL(u.String())
			}

			// 3. Query parameters only
			urlWithParams := b.BuildURL(entry, ctx) // This will replace placeholders if any

			// If URL has auth placeholders, they're already replaced
			if strings.Contains(entry.URL, "[USERNAME]") || strings.Contains(entry.URL, "[PASSWORD]") {
				addURL(urlWithParams)
			} else {
				// No placeholders - add query params for auth (don't overwrite existing params)
				if u, err := url.Parse(urlWithParams); err == nil {
					q := u.Query()

					// Add user/pwd if not already present
					if !q.Has("user") && !q.Has("usr") && !q.Has("username") {
						q.Set("user", ctx.Username)
					}
					if !q.Has("pwd") && !q.Has("password") && !q.Has("pass") {
						q.Set("pwd", ctx.Password)
					}
					u.RawQuery = q.Encode()
					addURL(u.String())

					// Try alternative names too
					q2 := url.Values{}
					for k, v := range u.Query() {
						q2[k] = v
					}
					if !q2.Has("username") && !q2.Has("user") && !q2.Has("usr") {
						q2.Set("username", ctx.Username)
					}
					if !q2.Has("password") && !q2.Has("pwd") && !q2.Has("pass") {
						q2.Set("password", ctx.Password)
					}
					u.RawQuery = q2.Encode()
					addURL(u.String())
				}
			}

			// 4. Basic Auth + Query parameters (combined)
			if strings.Contains(entry.URL, "[USERNAME]") || strings.Contains(entry.URL, "[PASSWORD]") {
				// URL has placeholders - add Basic Auth to the URL with replaced params
				if u, err := url.Parse(urlWithParams); err == nil {
					u.User = url.UserPassword(ctx.Username, ctx.Password)
					addURL(u.String())
				}
			} else {
				// No placeholders - add both Basic Auth and query params (without overwriting existing)
				if u, err := url.Parse(urlNoAuth); err == nil {
					u.User = url.UserPassword(ctx.Username, ctx.Password)
					q := u.Query()

					// Add auth params only if not already present
					if !q.Has("user") && !q.Has("usr") && !q.Has("username") {
						q.Set("user", ctx.Username)
					}
					if !q.Has("pwd") && !q.Has("password") && !q.Has("pass") {
						q.Set("pwd", ctx.Password)
					}
					u.RawQuery = q.Encode()
					addURL(u.String())
				}
			}
		} else {
			// No credentials provided - just one URL
			addURL(b.BuildURL(entry, ctx))
		}

	default:
		// Other protocols - single URL
		addURL(b.BuildURL(entry, ctx))
	}


	b.logger.Debug("BuildURLsFromEntry complete",
		"entry_url_pattern", entry.URL,
		"entry_type", entry.Type,
		"entry_protocol", entry.Protocol,
		"total_urls_generated", len(urls),
		"urls", urls)

	return urls
}