package sign

import (
	"fmt"
	"math/rand"
	"strings"
)

// Copy creates a deep copy of the sign with size randomization
func (s *Sign) Copy() *Sign {
	newSign := NewSign()

	// Copy spawn data
	if s.Spawn != nil {
		// Handle size randomization like the C++ version
		if s.Spawn.GetSizeOffset() > 0 {
			offset := s.Spawn.GetSizeOffset() + 1
			tmpSize := int32(s.Spawn.GetSize()) + (rand.Int31n(int32(offset)) - rand.Int31n(int32(offset)))

			if tmpSize < 0 {
				tmpSize = 1
			} else if tmpSize >= 0xFFFF {
				tmpSize = 0xFFFF
			}

			newSign.Spawn.SetSize(int16(tmpSize))
		} else {
			newSign.Spawn.SetSize(s.Spawn.GetSize())
		}

		// Copy other spawn properties
		newSign.Spawn.SetDatabaseID(s.Spawn.GetDatabaseID())
		newSign.Spawn.SetMerchantID(s.Spawn.GetMerchantID())
		newSign.Spawn.SetMerchantType(s.Spawn.GetMerchantType())
		// TODO: Copy appearance data when spawn system is fully integrated
		// TODO: Copy command lists when command system is integrated
		// TODO: Copy transporter ID, sounds, loot properties, etc.
	}

	// Copy sign-specific properties
	newSign.widgetID = s.widgetID
	newSign.widgetX = s.widgetX
	newSign.widgetY = s.widgetY
	newSign.widgetZ = s.widgetZ
	newSign.signType = s.signType
	newSign.title = s.title
	newSign.description = s.description
	newSign.language = s.language
	newSign.zoneX = s.zoneX
	newSign.zoneY = s.zoneY
	newSign.zoneZ = s.zoneZ
	newSign.zoneHeading = s.zoneHeading
	newSign.zoneID = s.zoneID
	newSign.signDistance = s.signDistance
	newSign.includeLocation = s.includeLocation
	newSign.includeHeading = s.includeHeading

	return newSign
}

// Serialize creates a packet for sending the sign to a client
func (s *Sign) Serialize(player Player, version int16) ([]byte, error) {
	// Delegate to spawn serialization
	if s.Spawn != nil {
		return s.Spawn.Serialize(player, version)
	}

	return nil, fmt.Errorf("spawn is nil")
}

// HandleUse processes player interaction with the sign
func (s *Sign) HandleUse(client Client, command string) error {
	if client == nil {
		return fmt.Errorf("client is nil")
	}

	player := client.GetPlayer()
	if player == nil {
		return fmt.Errorf("player is nil")
	}

	// Check quest requirements if this is from a client (not script)
	if !s.meetsQuestRequirements(client) {
		return nil // Silently fail if quest requirements not met
	}

	// Handle transporter functionality first
	if s.Spawn != nil && s.Spawn.GetTransporterID() > 0 {
		return s.handleTransporter(client)
	}

	// Handle zone transport signs
	if s.signType == SignTypeZone && s.zoneID > 0 {
		return s.handleZoneTransport(client)
	}

	// Handle entity commands
	if len(command) > 0 {
		return s.handleEntityCommand(client, command)
	}

	return nil
}

// meetsQuestRequirements checks if the player meets quest requirements to use the sign
func (s *Sign) meetsQuestRequirements(client Client) bool {
	// This is a placeholder implementation
	// In the full implementation, this would check:
	// - MeetsSpawnAccessRequirements(client.GetPlayer())
	// - GetQuestsRequiredOverride() flags
	// - appearance.show_command_icon

	// For now, assume all requirements are met
	return true
}

// handleTransporter processes transporter functionality
func (s *Sign) handleTransporter(client Client) error {
	zone := client.GetPlayer().GetZone()
	if zone == nil {
		return fmt.Errorf("player not in zone")
	}

	transporterID := s.Spawn.GetTransporterID()

	// Get transport destinations
	destinations, err := zone.GetTransporters(client, transporterID)
	if err != nil {
		return fmt.Errorf("failed to get transporters: %w", err)
	}

	if len(destinations) > 0 {
		client.SetTemporaryTransportID(0)
		return client.ProcessTeleport(s, destinations, transporterID)
	}

	return nil
}

// handleZoneTransport processes zone transport functionality
func (s *Sign) handleZoneTransport(client Client) error {
	player := client.GetPlayer()

	// Check distance if sign has distance requirement
	if s.signDistance > 0 {
		distance := player.GetDistance(s.Spawn)
		if distance > s.signDistance {
			client.SimpleMessage(ChannelColorYellow, "You are too far away!")
			return nil
		}
	}

	// Get zone name from database
	zoneName, err := client.GetDatabase().GetZoneName(s.zoneID)
	if err != nil || len(zoneName) == 0 {
		client.Message(ChannelColorYellow, "Unable to find zone with ID: %d", s.zoneID)
		return fmt.Errorf("zone not found: %d", s.zoneID)
	}

	// Check zone access
	if !client.CheckZoneAccess(zoneName) {
		return nil // Access denied (client handles message)
	}

	// Set coordinates if sign has valid zone coordinates
	useZoneDefaults := !s.HasZoneCoordinates()

	if !useZoneDefaults {
		player.SetX(s.zoneX)
		player.SetY(s.zoneY)
		player.SetZ(s.zoneZ)
		player.SetHeading(s.zoneHeading)
	} else {
		client.SimpleMessage(ChannelColorYellow, "Invalid zone in coords, taking you to a safe point.")
	}

	// Try instanced zone first, then regular zone
	if !client.TryZoneInstance(s.zoneID, useZoneDefaults) {
		return client.Zone(zoneName, useZoneDefaults)
	}

	return nil
}

// handleEntityCommand processes entity commands
func (s *Sign) handleEntityCommand(client Client, command string) error {
	if s.Spawn == nil {
		return fmt.Errorf("spawn is nil")
	}

	entityCommand := s.Spawn.FindEntityCommand(command)
	if entityCommand == nil {
		return nil // Command not found
	}

	// Handle mark command specially
	if strings.ToLower(entityCommand.Command) == "mark" {
		return s.handleMarkCommand(client)
	}

	// Process the entity command
	zone := client.GetCurrentZone()
	if zone == nil {
		return fmt.Errorf("player not in zone")
	}

	player := client.GetPlayer()
	target := player.GetTarget()

	return zone.ProcessEntityCommand(entityCommand, player, target)
}

// handleMarkCommand processes the mark command for marking signs
func (s *Sign) handleMarkCommand(client Client) error {
	charID := client.GetCharacterID()
	charName, err := client.GetDatabase().GetCharacterName(charID)
	if err != nil {
		return fmt.Errorf("failed to get character name: %w", err)
	}

	return client.GetDatabase().SaveSignMark(charID, s.widgetID, charName, client)
}

// GetDisplayText returns the formatted display text for the sign
func (s *Sign) GetDisplayText() string {
	var text strings.Builder

	if s.HasTitle() {
		text.WriteString(s.title)
	}

	if s.HasDescription() {
		if text.Len() > 0 {
			text.WriteByte('\n')
		}
		text.WriteString(s.description)
	}

	// Add location information if requested
	if s.includeLocation && s.HasZoneCoordinates() {
		if text.Len() > 0 {
			text.WriteByte('\n')
		}
		text.WriteString(fmt.Sprintf("Location: %.2f, %.2f, %.2f", s.zoneX, s.zoneY, s.zoneZ))
	}

	// Add heading information if requested
	if s.includeHeading && s.zoneHeading != 0 {
		if text.Len() > 0 {
			text.WriteByte('\n')
		}
		text.WriteString(fmt.Sprintf("Heading: %.2f", s.zoneHeading))
	}

	return text.String()
}

// Validate checks if the sign configuration is valid
func (s *Sign) Validate() []string {
	var issues []string

	if s.Spawn == nil {
		issues = append(issues, "Sign has no spawn data")
		return issues
	}

	if s.widgetID == 0 {
		issues = append(issues, "Sign has no widget ID")
	}

	if len(s.title) > MaxSignTitleLength {
		issues = append(issues, fmt.Sprintf("Sign title too long: %d > %d", len(s.title), MaxSignTitleLength))
	}

	if len(s.description) > MaxSignDescriptionLength {
		issues = append(issues, fmt.Sprintf("Sign description too long: %d > %d", len(s.description), MaxSignDescriptionLength))
	}

	if s.signType == SignTypeZone {
		if s.zoneID == 0 {
			issues = append(issues, "Zone sign has no zone ID")
		}

		if s.signDistance < 0 {
			issues = append(issues, "Sign distance cannot be negative")
		}
	}

	return issues
}

// IsValid returns true if the sign configuration is valid
func (s *Sign) IsValid() bool {
	issues := s.Validate()
	return len(issues) == 0
}
