package models

import (
	"encoding/xml"
	"fmt"
)

// Bass represents the response from /bass endpoint
type Bass struct {
	XMLName    xml.Name `xml:"bass"`
	DeviceID   string   `xml:"deviceID,attr"`
	TargetBass int      `xml:"targetbass"`
	ActualBass int      `xml:"actualbass"`
}

// BassRequest represents the request for POST /bass endpoint
type BassRequest struct {
	XMLName xml.Name `xml:"bass"`
	Level   int      `xml:",chardata"`
}

// Bass level constants
const (
	BassLevelMin     = -9
	BassLevelMax     = 9
	BassLevelDefault = 0
)

// NewBassRequest creates a new bass request with validation
func NewBassRequest(level int) (*BassRequest, error) {
	if !ValidateBassLevel(level) {
		return nil, fmt.Errorf("invalid bass level: %d (must be between %d and %d)", level, BassLevelMin, BassLevelMax)
	}

	return &BassRequest{
		Level: level,
	}, nil
}

// ValidateBassLevel validates that a bass level is within the allowed range
func ValidateBassLevel(level int) bool {
	return level >= BassLevelMin && level <= BassLevelMax
}

// ClampBassLevel clamps a bass level to the valid range
func ClampBassLevel(level int) int {
	if level < BassLevelMin {
		return BassLevelMin
	}

	if level > BassLevelMax {
		return BassLevelMax
	}

	return level
}

// GetLevel returns the target bass level
func (b *Bass) GetLevel() int {
	return b.TargetBass
}

// GetActualLevel returns the actual bass level
func (b *Bass) GetActualLevel() int {
	return b.ActualBass
}

// IsAtTarget returns true if actual bass matches target bass
func (b *Bass) IsAtTarget() bool {
	return b.TargetBass == b.ActualBass
}

// GetBassLevelName returns a descriptive name for the bass level
func GetBassLevelName(level int) string {
	switch {
	case level < -6:
		return "Very Low"
	case level < -3:
		return "Low"
	case level < 0:
		return "Slightly Low"
	case level == 0:
		return "Neutral"
	case level <= 3:
		return "Slightly High"
	case level <= 6:
		return "High"
	default:
		return "Very High"
	}
}

// GetBassLevelCategory returns the bass category
func GetBassLevelCategory(level int) string {
	switch {
	case level < 0:
		return "Bass Cut"
	case level == 0:
		return "Flat"
	default:
		return "Bass Boost"
	}
}

// String returns a human-readable string representation
func (b *Bass) String() string {
	return fmt.Sprintf("Bass: %d (%s)", b.GetLevel(), GetBassLevelName(b.GetLevel()))
}

// UnmarshalXML implements custom XML unmarshaling with validation
func (b *Bass) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	// Use a temporary struct to avoid infinite recursion
	type TempBass Bass

	temp := (*TempBass)(b)

	if err := d.DecodeElement(temp, &start); err != nil {
		return err
	}

	// Validate bass levels are within acceptable range
	if !ValidateBassLevel(b.TargetBass) {
		return fmt.Errorf("invalid target bass level: %d", b.TargetBass)
	}

	if !ValidateBassLevel(b.ActualBass) {
		return fmt.Errorf("invalid actual bass level: %d", b.ActualBass)
	}

	return nil
}

// MarshalXML implements custom XML marshaling
func (b *Bass) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	type TempBass Bass

	temp := (*TempBass)(b)

	return e.EncodeElement(temp, start)
}

// IsBassBoost returns true if bass is boosted (positive level)
func (b *Bass) IsBassBoost() bool {
	return b.GetLevel() > 0
}

// IsBassCut returns true if bass is cut (negative level)
func (b *Bass) IsBassCut() bool {
	return b.GetLevel() < 0
}

// IsFlat returns true if bass is neutral (zero level)
func (b *Bass) IsFlat() bool {
	return b.GetLevel() == 0
}

// GetBassChangeNeeded returns the amount of change needed to reach target from actual
func (b *Bass) GetBassChangeNeeded() int {
	return b.TargetBass - b.ActualBass
}

// BassCapabilities represents the response from /bassCapabilities endpoint
type BassCapabilities struct {
	XMLName       xml.Name `xml:"bassCapabilities"`
	DeviceID      string   `xml:"deviceID,attr"`
	BassAvailable bool     `xml:"bassAvailable"`
	BassMin       int      `xml:"bassMin"`
	BassMax       int      `xml:"bassMax"`
	BassDefault   int      `xml:"bassDefault"`
}

// IsBassSupported returns true if bass control is supported
func (bc *BassCapabilities) IsBassSupported() bool {
	return bc.BassAvailable
}

// GetMinLevel returns the minimum bass level
func (bc *BassCapabilities) GetMinLevel() int {
	return bc.BassMin
}

// GetMaxLevel returns the maximum bass level
func (bc *BassCapabilities) GetMaxLevel() int {
	return bc.BassMax
}

// GetDefaultLevel returns the default bass level
func (bc *BassCapabilities) GetDefaultLevel() int {
	return bc.BassDefault
}

// ValidateLevel returns true if the level is within supported range
func (bc *BassCapabilities) ValidateLevel(level int) bool {
	return level >= bc.BassMin && level <= bc.BassMax
}

// ClampLevel clamps a level to the supported range
func (bc *BassCapabilities) ClampLevel(level int) int {
	if level < bc.BassMin {
		return bc.BassMin
	}

	if level > bc.BassMax {
		return bc.BassMax
	}

	return level
}

// String returns a human-readable string representation
func (bc *BassCapabilities) String() string {
	if !bc.BassAvailable {
		return "Bass control not supported"
	}

	return fmt.Sprintf("Bass: %d to %d (default: %d)", bc.BassMin, bc.BassMax, bc.BassDefault)
}
