package main

import (
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/gesellix/bose-soundtouch/pkg/models"
	"github.com/urfave/cli/v2"
)

func printSource(source models.SourceItem) {
	fmt.Printf("    • %s", source.GetDisplayName())

	if source.SourceAccount != "" && source.SourceAccount != source.Source {
		fmt.Printf(" (%s)", source.SourceAccount)
	}

	var attributes []string
	if source.IsLocalSource() {
		attributes = append(attributes, "Local")
		attributes = append(attributes, "Available")
	}

	if len(attributes) > 0 {
		fmt.Printf(" [%s]", strings.Join(attributes, ", "))
	}

	fmt.Println()
}

// listSources handles listing available audio sources
func listSources(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	PrintDeviceHeader("Getting available sources", clientConfig.Host, clientConfig.Port)

	sources, err := client.GetSources()
	if err != nil {
		return fmt.Errorf("failed to get sources: %w", err)
	}

	fmt.Printf("Available Audio Sources:\n")
	fmt.Printf("  Device ID: %s\n", sources.DeviceID)

	// Show ready sources first
	availableSources := sources.GetAvailableSources()
	if len(availableSources) > 0 {
		fmt.Printf("  Ready Sources:\n")

		for _, source := range availableSources {
			printSource(source)
		}
	}

	// Show all configured sources
	fmt.Printf("  All Sources:\n")

	for _, source := range sources.SourceItem {
		status := "Available"
		if !source.IsLocalSource() {
			status = "Remote"
		}

		fmt.Printf("    • %s (%s)\n", source.GetDisplayName(), status)

		if source.SourceAccount != "" && source.SourceAccount != source.Source {
			fmt.Printf("      Account: %s\n", source.SourceAccount)
		}
	}

	// Show streaming sources
	streamingSources := sources.GetStreamingSources()
	if len(streamingSources) > 0 {
		fmt.Printf("  Streaming Services:\n")

		for _, source := range streamingSources {
			fmt.Printf("    • %s", source.GetDisplayName())

			if source.SourceAccount != "" {
				fmt.Printf(" (%s)", source.SourceAccount)
			}

			fmt.Println()
		}
	}

	// Show service availability summary
	fmt.Println()

	checker := NewServiceAvailabilityChecker(client)
	checker.PrintServiceAvailabilitySummary()

	return nil
}

// selectSource handles selecting an audio source
func selectSource(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	sourceName := strings.ToUpper(c.String("source"))
	sourceAccount := c.String("account")

	// Check service availability
	checker := NewServiceAvailabilityChecker(client)

	actionDescription := fmt.Sprintf("select %s source", strings.ToLower(sourceName))
	if !checker.CheckSourceAvailable(sourceName, actionDescription) {
		return fmt.Errorf("source '%s' is not available", sourceName)
	}

	PrintDeviceHeader(fmt.Sprintf("Selecting source '%s'", sourceName), clientConfig.Host, clientConfig.Port)

	err = client.SelectSource(sourceName, sourceAccount)
	if err != nil {
		return fmt.Errorf("failed to select source: %w", err)
	}

	if sourceAccount != "" {
		PrintSuccess(fmt.Sprintf("Source '%s' with account '%s' selected", sourceName, sourceAccount))
	} else {
		PrintSuccess(fmt.Sprintf("Source '%s' selected", sourceName))
	}

	return nil
}

// selectSpotify handles selecting Spotify source
func selectSpotify(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	// Check Spotify availability
	checker := NewServiceAvailabilityChecker(client)
	if !checker.ValidateSpotifyAvailable("select Spotify source") {
		return fmt.Errorf("spotify is not available on this device")
	}

	PrintDeviceHeader("Selecting Spotify source", clientConfig.Host, clientConfig.Port)

	err = client.SelectSpotify("")
	if err != nil {
		return fmt.Errorf("failed to select Spotify: %w", err)
	}

	PrintSuccess("Spotify source selected")

	return nil
}

// selectBluetooth handles selecting Bluetooth source
func selectBluetooth(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	// Check Bluetooth availability
	checker := NewServiceAvailabilityChecker(client)
	if !checker.ValidateBluetoothAvailable("select Bluetooth source") {
		return fmt.Errorf("bluetooth is not available on this device")
	}

	PrintDeviceHeader("Selecting Bluetooth source", clientConfig.Host, clientConfig.Port)

	err = client.SelectBluetooth()
	if err != nil {
		return fmt.Errorf("failed to select Bluetooth: %w", err)
	}

	PrintSuccess("Bluetooth source selected")

	return nil
}

// selectAux handles selecting AUX input source
func selectAux(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	PrintDeviceHeader("Selecting AUX input source", clientConfig.Host, clientConfig.Port)

	err = client.SelectAux()
	if err != nil {
		return fmt.Errorf("failed to select AUX: %w", err)
	}

	PrintSuccess("AUX input source selected")

	return nil
}

// selectLocalInternetRadio handles selecting LOCAL_INTERNET_RADIO source
func selectLocalInternetRadio(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	location := c.String("location")
	if location == "" {
		return fmt.Errorf("location is required (use --location)")
	}

	sourceAccount := c.String("account")
	itemName := c.String("name")
	containerArt := c.String("artwork")

	// Check LOCAL_INTERNET_RADIO availability
	checker := NewServiceAvailabilityChecker(client)
	if !checker.CheckSourceAvailable("LOCAL_INTERNET_RADIO", "select internet radio") {
		return fmt.Errorf("LOCAL_INTERNET_RADIO is not available")
	}

	PrintDeviceHeader("Selecting internet radio stream", clientConfig.Host, clientConfig.Port)

	if itemName != "" {
		fmt.Printf("  Station: %s\n", itemName)
	}

	fmt.Printf("  Location: %s\n", location)

	err = client.SelectLocalInternetRadio(location, sourceAccount, itemName, containerArt)
	if err != nil {
		return fmt.Errorf("failed to select internet radio: %w", err)
	}

	PrintSuccess("Internet radio stream selected")

	return nil
}

// selectCustomRadio handles selecting custom radio stream via soundtouch-service
func selectCustomRadio(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	streamURL := c.String("url")
	itemName := c.String("name")
	containerArt := c.String("artwork")
	serviceURL := c.String("service-url")

	encodedURL := base64.URLEncoding.EncodeToString([]byte(streamURL))
	location := fmt.Sprintf("%s/custom/v1/playback/%s", serviceURL, encodedURL)

	params := url.Values{}
	if itemName != "" {
		params.Add("name", itemName)
	}

	if containerArt != "" {
		params.Add("imageUrl", containerArt)
	}

	if len(params) > 0 {
		location += "?" + params.Encode()
	}

	// Check LOCAL_INTERNET_RADIO availability
	checker := NewServiceAvailabilityChecker(client)
	if !checker.CheckSourceAvailable("LOCAL_INTERNET_RADIO", "select custom radio") {
		return fmt.Errorf("LOCAL_INTERNET_RADIO is not available")
	}

	PrintDeviceHeader("Selecting custom radio stream", clientConfig.Host, clientConfig.Port)

	if itemName != "" {
		fmt.Printf("  Station: %s\n", itemName)
	}

	fmt.Printf("  URL: %s\n", streamURL)
	fmt.Printf("  Proxy: %s\n", location)

	err = client.SelectLocalInternetRadio(location, "", itemName, containerArt)
	if err != nil {
		return fmt.Errorf("failed to select custom radio: %w", err)
	}

	PrintSuccess("Custom radio stream selected")

	return nil
}

// selectLocalMusic handles selecting LOCAL_MUSIC source
func selectLocalMusic(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	location := c.String("location")
	if location == "" {
		return fmt.Errorf("location is required (use --location)")
	}

	sourceAccount := c.String("account")
	if sourceAccount == "" {
		return fmt.Errorf("account is required for LOCAL_MUSIC (use --account)")
	}

	itemName := c.String("name")
	containerArt := c.String("artwork")

	// Check LOCAL_MUSIC availability
	checker := NewServiceAvailabilityChecker(client)
	if !checker.CheckSourceAvailable("LOCAL_MUSIC", "select local music") {
		return fmt.Errorf("LOCAL_MUSIC is not available")
	}

	PrintDeviceHeader("Selecting local music content", clientConfig.Host, clientConfig.Port)

	if itemName != "" {
		fmt.Printf("  Content: %s\n", itemName)
	}

	fmt.Printf("  Location: %s\n", location)
	fmt.Printf("  Account: %s\n", sourceAccount)

	err = client.SelectLocalMusic(location, sourceAccount, itemName, containerArt)
	if err != nil {
		return fmt.Errorf("failed to select local music: %w", err)
	}

	PrintSuccess("Local music content selected")

	return nil
}

// selectStoredMusic handles selecting STORED_MUSIC source
func selectStoredMusic(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	location := c.String("location")
	if location == "" {
		return fmt.Errorf("location is required (use --location)")
	}

	sourceAccount := c.String("account")
	if sourceAccount == "" {
		return fmt.Errorf("account is required for STORED_MUSIC (use --account)")
	}

	itemName := c.String("name")
	containerArt := c.String("artwork")

	// Check STORED_MUSIC availability
	checker := NewServiceAvailabilityChecker(client)
	if !checker.CheckSourceAvailable("STORED_MUSIC", "select stored music") {
		return fmt.Errorf("STORED_MUSIC is not available")
	}

	PrintDeviceHeader("Selecting stored music content", clientConfig.Host, clientConfig.Port)

	if itemName != "" {
		fmt.Printf("  Content: %s\n", itemName)
	}

	fmt.Printf("  Location: %s\n", location)
	fmt.Printf("  Account: %s\n", sourceAccount)

	err = client.SelectStoredMusic(location, sourceAccount, itemName, containerArt)
	if err != nil {
		return fmt.Errorf("failed to select stored music: %w", err)
	}

	PrintSuccess("Stored music content selected")

	return nil
}

// selectContent handles selecting content using a ContentItem directly
func selectContent(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	// Required parameters
	source := strings.ToUpper(c.String("source"))
	if source == "" {
		return fmt.Errorf("source is required (use --source)")
	}

	location := c.String("location")
	if location == "" {
		return fmt.Errorf("location is required (use --location)")
	}

	// Optional parameters
	sourceAccount := c.String("account")
	itemName := c.String("name")
	containerArt := c.String("artwork")
	itemType := c.String("type")
	isPresetable := c.Bool("presetable")

	// Create ContentItem
	contentItem := &models.ContentItem{
		Source:        source,
		Type:          itemType,
		Location:      location,
		SourceAccount: sourceAccount,
		IsPresetable:  isPresetable,
		ItemName:      itemName,
		ContainerArt:  containerArt,
	}

	// Set default type if not specified
	if itemType == "" {
		switch source {
		case "SPOTIFY":
			contentItem.Type = "uri"
		case "TUNEIN", "LOCAL_INTERNET_RADIO":
			contentItem.Type = "stationurl"
		case "LOCAL_MUSIC":
			contentItem.Type = "album" // default, could be track, artist, etc.
		}
	}

	// Set default item name if not specified
	if itemName == "" {
		contentItem.ItemName = source
	}

	PrintDeviceHeader("Selecting content", clientConfig.Host, clientConfig.Port)

	fmt.Printf("  Source: %s\n", source)
	fmt.Printf("  Location: %s\n", location)

	if sourceAccount != "" {
		fmt.Printf("  Account: %s\n", sourceAccount)
	}

	if itemName != "" {
		fmt.Printf("  Name: %s\n", itemName)
	}

	if itemType != "" {
		fmt.Printf("  Type: %s\n", itemType)
	}

	err = client.SelectContentItem(contentItem)
	if err != nil {
		return fmt.Errorf("failed to select content: %w", err)
	}

	PrintSuccess("Content selected")

	return nil
}

// getServiceAvailability handles displaying service availability information
func getServiceAvailability(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	PrintDeviceHeader("Getting service availability", clientConfig.Host, clientConfig.Port)

	serviceAvailability, err := client.GetServiceAvailability()
	if err != nil {
		return fmt.Errorf("failed to get service availability: %w", err)
	}

	fmt.Printf("Service Availability Report:\n")
	fmt.Printf("  Total Services: %d\n", serviceAvailability.GetServiceCount())
	fmt.Printf("  Available Services: %d\n", serviceAvailability.GetAvailableServiceCount())
	fmt.Printf("  Unavailable Services: %d\n", serviceAvailability.GetUnavailableServiceCount())

	// Show available services
	fmt.Printf("\n✅ Available Services:\n")

	availableServices := serviceAvailability.GetAvailableServices()
	if len(availableServices) == 0 {
		fmt.Printf("    None\n")
	} else {
		for _, service := range availableServices {
			fmt.Printf("    • %s\n", formatServiceTypeForDisplay(models.ServiceType(service.Type)))
		}
	}

	// Show unavailable services with reasons
	fmt.Printf("\n❌ Unavailable Services:\n")

	unavailableServices := serviceAvailability.GetUnavailableServices()
	if len(unavailableServices) == 0 {
		fmt.Printf("    None\n")
	} else {
		for _, service := range unavailableServices {
			reason := ""
			if service.Reason != "" {
				reason = fmt.Sprintf(" (%s)", service.Reason)
			}

			fmt.Printf("    • %s%s\n", formatServiceTypeForDisplay(models.ServiceType(service.Type)), reason)
		}
	}

	// Show service categories
	fmt.Printf("\n🎵 Streaming Services:\n")

	streamingServices := serviceAvailability.GetStreamingServices()
	availableCount := 0

	for _, service := range streamingServices {
		status := "❌"
		if service.IsAvailable {
			status = "✅"
			availableCount++
		}

		fmt.Printf("    %s %s\n", status, formatServiceTypeForDisplay(models.ServiceType(service.Type)))
	}

	fmt.Printf("    Summary: %d/%d streaming services available\n", availableCount, len(streamingServices))

	fmt.Printf("\n🔗 Local Input Services:\n")

	localServices := serviceAvailability.GetLocalServices()
	localAvailableCount := 0

	for _, service := range localServices {
		status := "❌"
		if service.IsAvailable {
			status = "✅"
			localAvailableCount++
		}

		fmt.Printf("    %s %s\n", status, formatServiceTypeForDisplay(models.ServiceType(service.Type)))
	}

	fmt.Printf("    Summary: %d/%d local services available\n", localAvailableCount, len(localServices))

	return nil
}

// compareSourcesAndAvailability compares configured sources with service availability
func compareSourcesAndAvailability(c *cli.Context) error {
	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	PrintDeviceHeader("Comparing sources and service availability", clientConfig.Host, clientConfig.Port)

	// Get both sources and service availability
	sources, err := client.GetSources()
	if err != nil {
		return fmt.Errorf("failed to get sources: %w", err)
	}

	serviceAvailability, err := client.GetServiceAvailability()
	if err != nil {
		return fmt.Errorf("failed to get service availability: %w", err)
	}

	fmt.Printf("Source vs Availability Comparison:\n\n")

	performSourceComparisons(sources, serviceAvailability)
	printSourceSummary(sources, serviceAvailability)

	return nil
}

// performSourceComparisons compares configured sources with availability
func performSourceComparisons(sources *models.Sources, serviceAvailability *models.ServiceAvailability) {
	// Check key services
	comparisons := []struct {
		name                 string
		configuredCheck      func() bool
		availableCheck       func() bool
		getConfiguredSources func() []models.SourceItem
	}{
		{
			"Spotify",
			sources.HasSpotify,
			serviceAvailability.HasSpotify,
			sources.GetSpotifySources,
		},
		{
			"Bluetooth",
			sources.HasBluetooth,
			serviceAvailability.HasBluetooth,
			func() []models.SourceItem { return sources.GetSourcesByType("BLUETOOTH") },
		},
	}

	for _, comp := range comparisons {
		compareServiceStatus(comp.name, comp.configuredCheck(), comp.availableCheck(), serviceAvailability)
	}
}

// compareServiceStatus compares a single service's configuration vs availability
func compareServiceStatus(serviceName string, configured, available bool, serviceAvailability *models.ServiceAvailability) {
	fmt.Printf("🔍 %s:\n", serviceName)
	fmt.Printf("    Configured: %s\n", boolToStatus(configured))
	fmt.Printf("    Available: %s\n", boolToStatus(available))

	switch {
	case available && !configured:
		fmt.Printf("    💡 %s is available but not configured - consider setting it up\n", serviceName)
	case configured && !available:
		fmt.Printf("    ⚠️  %s is configured but not available - check device status\n", serviceName)
		printServiceUnavailableReason(serviceName, serviceAvailability)
	case configured && available:
		fmt.Printf("    ✅ %s is properly configured and available\n", serviceName)
	default:
		fmt.Printf("    ➖ %s is neither configured nor available\n", serviceName)
	}

	fmt.Println()
}

// printServiceUnavailableReason prints the reason why a service is unavailable
func printServiceUnavailableReason(serviceName string, serviceAvailability *models.ServiceAvailability) {
	var service *models.Service

	switch serviceName {
	case "Spotify":
		service = serviceAvailability.GetServiceByType(models.ServiceTypeSpotify)
	case "Bluetooth":
		service = serviceAvailability.GetServiceByType(models.ServiceTypeBluetooth)
	}

	if service != nil && service.Reason != "" {
		fmt.Printf("    📝 Reason: %s\n", service.Reason)
	}
}

// printSourceSummary prints a summary of sources and services
func printSourceSummary(sources *models.Sources, serviceAvailability *models.ServiceAvailability) {
	// Summary
	fmt.Printf("📊 Summary:\n")
	fmt.Printf("    Total configured sources: %d\n", sources.GetSourceCount())
	fmt.Printf("    Ready configured sources: %d\n", sources.GetReadySourceCount())
	fmt.Printf("    Total available services: %d\n", serviceAvailability.GetAvailableServiceCount())
	fmt.Printf("    Total possible services: %d\n", serviceAvailability.GetServiceCount())
}

// boolToStatus converts boolean to user-friendly status
func boolToStatus(b bool) string {
	if b {
		return "✅ Yes"
	}

	return "❌ No"
}

// notifySourcesUpdated POSTs a sourcesUpdated notification directly to the
// speaker's :8090/notification endpoint. The speaker re-fetches its source
// list from AfterTouch immediately. Requires network access to the speaker.
func notifySourcesUpdated(c *cli.Context) error {
	if err := RequireHost(c); err != nil {
		return err
	}

	clientConfig := GetClientConfig(c)

	client, err := CreateSoundTouchClient(clientConfig)
	if err != nil {
		return err
	}

	info, err := client.GetDeviceInfo()
	if err != nil {
		return fmt.Errorf("failed to get device info from %s: %w", clientConfig.Host, err)
	}

	body := fmt.Sprintf(`<updates deviceID="%s"><sourcesUpdated/></updates>`, info.DeviceID)
	notifyURL := fmt.Sprintf("http://%s:8090/notification", clientConfig.Host)

	req, err := http.NewRequest(http.MethodPost, notifyURL, strings.NewReader(body))
	if err != nil {
		return fmt.Errorf("build request: %w", err)
	}

	req.Header.Set("Content-Type", "application/xml")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("post to speaker: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode >= 300 {
		respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<10))

		return fmt.Errorf("speaker returned %d: %s", resp.StatusCode, strings.TrimSpace(string(respBody)))
	}

	PrintSuccess(fmt.Sprintf("Sent sourcesUpdated to %s (%s)", info.DeviceID, clientConfig.Host))

	return nil
}
