package fbdraw

import (
	"fmt"
	"log"
	"os"
	"syscall"
	"unsafe"
)

// FBDisplay renders to a Linux framebuffer device
type FBDisplay struct {
	device     string
	file       *os.File
	info       fbVarScreeninfo
	fixInfo    fbFixScreeninfo
	data       []byte
	charWidth  int
	charHeight int
}

// fbFixScreeninfo from linux/fb.h
type fbFixScreeninfo struct {
	ID           [16]byte
	SMEMStart    uint64
	SMEMLen      uint32
	Type         uint32
	TypeAux      uint32
	Visual       uint32
	XPanStep     uint16
	YPanStep     uint16
	YWrapStep    uint16
	_            uint16
	LineLength   uint32
	MMIOStart    uint64
	MMIOLen      uint32
	Accel        uint32
	Capabilities uint16
	Reserved     [2]uint16
}

// fbVarScreeninfo from linux/fb.h
type fbVarScreeninfo struct {
	XRes         uint32
	YRes         uint32
	XResVirtual  uint32
	YResVirtual  uint32
	XOffset      uint32
	YOffset      uint32
	BitsPerPixel uint32
	Grayscale    uint32
	Red          fbBitfield
	Green        fbBitfield
	Blue         fbBitfield
	Transp       fbBitfield
	NonStd       uint32
	Activate     uint32
	Height       uint32
	Width        uint32
	AccelFlags   uint32
	PixClock     uint32
	LeftMargin   uint32
	RightMargin  uint32
	UpperMargin  uint32
	LowerMargin  uint32
	HSyncLen     uint32
	VSyncLen     uint32
	Sync         uint32
	VMode        uint32
	Rotate       uint32
	Colorspace   uint32
	Reserved     [4]uint32
}

type fbBitfield struct {
	Offset uint32
	Length uint32
	Right  uint32
}

const (
	fbiogetVscreeninfo = 0x4600
	fbiogetFscreeninfo = 0x4602
)

// NewFBDisplay creates a display for a specific framebuffer device
func NewFBDisplay(device string) (*FBDisplay, error) {
	file, err := os.OpenFile(device, os.O_RDWR, 0) //nolint:gosec
	if err != nil {
		return nil, fmt.Errorf("opening framebuffer %s: %w", device, err)
	}

	display := &FBDisplay{
		device: device,
		file:   file,
	}

	// Get variable screen info
	if err := display.getScreenInfo(); err != nil {
		_ = file.Close()
		return nil, err
	}

	// Get fixed screen info
	if err := display.getFixedInfo(); err != nil {
		_ = file.Close()
		return nil, err
	}

	// Memory map the framebuffer
	size := int(display.fixInfo.SMEMLen)
	display.data, err = syscall.Mmap(
		int(file.Fd()),
		0,
		size,
		syscall.PROT_READ|syscall.PROT_WRITE,
		syscall.MAP_SHARED,
	)
	if err != nil {
		_ = file.Close()
		return nil, fmt.Errorf("mmap framebuffer: %w", err)
	}

	// Calculate character dimensions (rough approximation)
	display.charWidth = 8
	display.charHeight = 16

	return display, nil
}

// NewFBDisplayAuto auto-detects and opens the framebuffer
func NewFBDisplayAuto() (*FBDisplay, error) {
	// Try common framebuffer devices
	devices := []string{"/dev/fb0", "/dev/fb1", "/dev/graphics/fb0"}

	for _, device := range devices {
		if _, err := os.Stat(device); err == nil {
			display, err := NewFBDisplay(device)
			if err == nil {
				return display, nil
			}
		}
	}

	return nil, fmt.Errorf("no framebuffer device found")
}

func (d *FBDisplay) getScreenInfo() error {
	_, _, errno := syscall.Syscall(
		syscall.SYS_IOCTL,
		d.file.Fd(),
		fbiogetVscreeninfo,
		uintptr(unsafe.Pointer(&d.info)), //nolint:gosec
	)
	if errno != 0 {
		return fmt.Errorf("ioctl FBIOGET_VSCREENINFO: %w", errno)
	}
	return nil
}

func (d *FBDisplay) getFixedInfo() error {
	_, _, errno := syscall.Syscall(
		syscall.SYS_IOCTL,
		d.file.Fd(),
		fbiogetFscreeninfo,
		uintptr(unsafe.Pointer(&d.fixInfo)), //nolint:gosec
	)
	if errno != 0 {
		return fmt.Errorf("ioctl FBIOGET_FSCREENINFO: %w", errno)
	}
	return nil
}

// Write renders a grid to the framebuffer
func (d *FBDisplay) Write(grid *CharGrid) error {
	// Render grid to image
	img, err := grid.Render()
	if err != nil {
		return fmt.Errorf("rendering grid: %w", err)
	}

	// Clear framebuffer
	for i := range d.data {
		d.data[i] = 0
	}

	// Copy image to framebuffer
	bounds := img.Bounds()
	bytesPerPixel := int(d.info.BitsPerPixel / 8)
	lineLength := int(d.fixInfo.LineLength)

	for y := bounds.Min.Y; y < bounds.Max.Y && y < int(d.info.YRes); y++ {
		for x := bounds.Min.X; x < bounds.Max.X && x < int(d.info.XRes); x++ {
			r, g, b, _ := img.At(x, y).RGBA()

			offset := y*lineLength + x*bytesPerPixel
			if offset+bytesPerPixel <= len(d.data) {
				// Assuming 32-bit BGRA format (most common)
				if bytesPerPixel == 4 {
					d.data[offset+0] = byte(b >> 8)
					d.data[offset+1] = byte(g >> 8)
					d.data[offset+2] = byte(r >> 8)
					d.data[offset+3] = 0xFF
				}
			}
		}
	}

	return nil
}

// Size returns the display size in characters
func (d *FBDisplay) Size() (width, height int) {
	width = int(d.info.XRes) / d.charWidth
	height = int(d.info.YRes) / d.charHeight
	return
}

// Close closes the framebuffer
func (d *FBDisplay) Close() error {
	if d.data != nil {
		if err := syscall.Munmap(d.data); err != nil {
			log.Printf("munmap error: %v", err)
		}
		d.data = nil
	}

	if d.file != nil {
		if err := d.file.Close(); err != nil {
			return err
		}
		d.file = nil
	}

	return nil
}

// TerminalDisplay renders to the terminal using ANSI escape codes
type TerminalDisplay struct {
	width  int
	height int
}

// NewTerminalDisplay creates a terminal display
func NewTerminalDisplay(width, height int) *TerminalDisplay {
	return &TerminalDisplay{
		width:  width,
		height: height,
	}
}

// Write renders a grid to the terminal
func (d *TerminalDisplay) Write(grid *CharGrid) error {
	// Clear screen
	fmt.Print("\033[2J\033[H")

	// Print ANSI representation
	fmt.Print(grid.ToANSI())

	return nil
}

// Size returns the terminal size in characters
func (d *TerminalDisplay) Size() (width, height int) {
	return d.width, d.height
}

// Close is a no-op for terminal display
func (d *TerminalDisplay) Close() error {
	// Clear screen one last time
	fmt.Print("\033[2J\033[H")
	return nil
}

// LogDisplay renders to a logger for debugging
type LogDisplay struct {
	width  int
	height int
	logger *log.Logger
}

// NewLogDisplay creates a log display
func NewLogDisplay(width, height int, logger *log.Logger) *LogDisplay {
	if logger == nil {
		logger = log.New(os.Stderr, "[fbdraw] ", log.LstdFlags)
	}

	return &LogDisplay{
		width:  width,
		height: height,
		logger: logger,
	}
}

// Write logs the grid as text
func (d *LogDisplay) Write(grid *CharGrid) error {
	d.logger.Printf("=== Frame ===\n%s\n", grid.ToText())
	return nil
}

// Size returns the display size
func (d *LogDisplay) Size() (width, height int) {
	return d.width, d.height
}

// Close is a no-op for log display
func (d *LogDisplay) Close() error {
	return nil
}
