package rdp

import (
	"fmt"
	"math"
	"sync"
	"time"
)

// MockBackend implements the RDPBackend interface for development and testing.
// Instead of connecting to a real RDP server, it generates animated test frames
// with a gradient pattern, moving shapes, and a timestamp overlay.
type MockBackend struct {
	connected bool
	config    RDPConfig
	buffer    *PixelBuffer
	mu        sync.Mutex
	startTime time.Time
	clipboard string
}

// NewMockBackend creates a new MockBackend instance.
func NewMockBackend() *MockBackend {
	return &MockBackend{}
}

// Connect initializes the mock backend with the given configuration and starts
// generating test frames.
func (m *MockBackend) Connect(config RDPConfig) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.connected {
		return fmt.Errorf("already connected")
	}

	width := config.Width
	height := config.Height
	if width <= 0 {
		width = 1920
	}
	if height <= 0 {
		height = 1080
	}

	m.config = config
	m.config.Width = width
	m.config.Height = height
	m.buffer = NewPixelBuffer(width, height)
	m.connected = true
	m.startTime = time.Now()

	// Generate the initial test frame
	m.generateFrame()

	return nil
}

// Disconnect shuts down the mock backend.
func (m *MockBackend) Disconnect() error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if !m.connected {
		return fmt.Errorf("not connected")
	}

	m.connected = false
	m.buffer = nil
	return nil
}

// SendMouseEvent records a mouse event (no-op for mock).
func (m *MockBackend) SendMouseEvent(x, y int, flags uint32) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if !m.connected {
		return fmt.Errorf("not connected")
	}

	// In the mock, we draw a cursor indicator at the mouse position
	if m.buffer != nil && flags&MouseFlagMove != 0 {
		m.drawCursor(x, y)
	}

	return nil
}

// SendKeyEvent records a key event (no-op for mock).
func (m *MockBackend) SendKeyEvent(scancode uint32, pressed bool) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if !m.connected {
		return fmt.Errorf("not connected")
	}
	return nil
}

// SendClipboard stores clipboard text (mock implementation).
func (m *MockBackend) SendClipboard(data string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if !m.connected {
		return fmt.Errorf("not connected")
	}

	m.clipboard = data
	return nil
}

// GetFrame returns the current test frame. Each call regenerates the frame
// with an updated animation state so the renderer can verify dynamic updates.
func (m *MockBackend) GetFrame() ([]byte, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if !m.connected {
		return nil, fmt.Errorf("not connected")
	}

	m.generateFrame()
	return m.buffer.GetFrame(), nil
}

// IsConnected reports whether the mock backend is connected.
func (m *MockBackend) IsConnected() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.connected
}

// generateFrame creates a visually interesting test frame with:
//   - A blue-to-purple diagonal gradient background
//   - An animated bouncing rectangle
//   - A grid overlay for alignment verification
//   - Session info text area
func (m *MockBackend) generateFrame() {
	w := m.config.Width
	h := m.config.Height
	elapsed := time.Since(m.startTime).Seconds()

	data := make([]byte, w*h*4)

	// ── Background: diagonal gradient from dark blue to dark purple ──
	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			offset := (y*w + x) * 4
			// Normalized coordinates
			nx := float64(x) / float64(w)
			ny := float64(y) / float64(h)
			diag := (nx + ny) / 2.0

			r := uint8(20 + diag*40)                      // 20–60
			g := uint8(25 + (1.0-diag)*30)                 // 25–55
			b := uint8(80 + diag*100)                       // 80–180
			data[offset+0] = r
			data[offset+1] = g
			data[offset+2] = b
			data[offset+3] = 255
		}
	}

	// ── Grid overlay: subtle lines every 100 pixels ──
	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			if x%100 == 0 || y%100 == 0 {
				offset := (y*w + x) * 4
				data[offset+0] = min8(data[offset+0]+20, 255)
				data[offset+1] = min8(data[offset+1]+20, 255)
				data[offset+2] = min8(data[offset+2]+20, 255)
			}
		}
	}

	// ── Animated bouncing rectangle ──
	rectW, rectH := 200, 120
	// Bounce horizontally and vertically using sine/cosine
	cx := int(float64(w-rectW) * (0.5 + 0.4*math.Sin(elapsed*0.7)))
	cy := int(float64(h-rectH) * (0.5 + 0.4*math.Cos(elapsed*0.5)))

	// Color cycles through hues
	hue := math.Mod(elapsed*30, 360)
	rr, gg, bb := hueToRGB(hue)

	for ry := 0; ry < rectH; ry++ {
		for rx := 0; rx < rectW; rx++ {
			px := cx + rx
			py := cy + ry
			if px >= 0 && px < w && py >= 0 && py < h {
				offset := (py*w + px) * 4
				// Border: 3px white outline
				if rx < 3 || rx >= rectW-3 || ry < 3 || ry >= rectH-3 {
					data[offset+0] = 255
					data[offset+1] = 255
					data[offset+2] = 255
					data[offset+3] = 255
				} else {
					data[offset+0] = rr
					data[offset+1] = gg
					data[offset+2] = bb
					data[offset+3] = 220
				}
			}
		}
	}

	// ── Info panel in top-left corner ──
	panelW, panelH := 320, 80
	for py := 10; py < 10+panelH && py < h; py++ {
		for px := 10; px < 10+panelW && px < w; px++ {
			offset := (py*w + px) * 4
			data[offset+0] = 0
			data[offset+1] = 0
			data[offset+2] = 0
			data[offset+3] = 180
		}
	}

	// ── Secondary animated element: pulsing circle ──
	circleX := w / 2
	circleY := h / 2
	radius := 40.0 + 20.0*math.Sin(elapsed*2.0)

	for dy := -70; dy <= 70; dy++ {
		for dx := -70; dx <= 70; dx++ {
			dist := math.Sqrt(float64(dx*dx + dy*dy))
			if dist <= radius && dist >= radius-4 {
				px := circleX + dx
				py := circleY + dy
				if px >= 0 && px < w && py >= 0 && py < h {
					offset := (py*w + px) * 4
					data[offset+0] = 88
					data[offset+1] = 166
					data[offset+2] = 255
					data[offset+3] = 255
				}
			}
		}
	}

	// Apply as a full-frame update
	m.buffer.Update(0, 0, w, h, data)
}

// drawCursor draws a small crosshair at the given position.
func (m *MockBackend) drawCursor(cx, cy int) {
	size := 10
	w := m.config.Width
	h := m.config.Height

	cursorData := make([]byte, (size*2+1)*(size*2+1)*4)
	idx := 0
	for dy := -size; dy <= size; dy++ {
		for dx := -size; dx <= size; dx++ {
			if dx == 0 || dy == 0 {
				cursorData[idx+0] = 255
				cursorData[idx+1] = 255
				cursorData[idx+2] = 0
				cursorData[idx+3] = 200
			}
			idx += 4
		}
	}

	startX := cx - size
	startY := cy - size
	if startX < 0 {
		startX = 0
	}
	if startY < 0 {
		startY = 0
	}
	_ = w
	_ = h

	m.buffer.Update(startX, startY, size*2+1, size*2+1, cursorData)
}

// hueToRGB converts a hue angle (0-360) to RGB values.
func hueToRGB(hue float64) (uint8, uint8, uint8) {
	h := math.Mod(hue, 360) / 60
	c := 200.0 // chroma
	x := c * (1 - math.Abs(math.Mod(h, 2)-1))

	var r, g, b float64
	switch {
	case h < 1:
		r, g, b = c, x, 0
	case h < 2:
		r, g, b = x, c, 0
	case h < 3:
		r, g, b = 0, c, x
	case h < 4:
		r, g, b = 0, x, c
	case h < 5:
		r, g, b = x, 0, c
	default:
		r, g, b = c, 0, x
	}

	return uint8(r + 55), uint8(g + 55), uint8(b + 55)
}

// min8 returns the smaller of two uint8 values.
func min8(a, b uint8) uint8 {
	if a < b {
		return a
	}
	return b
}
