package lifx

import (
	"context"
	"fmt"
	"git.aiterp.net/lucifer/new-server/models"
	"net"
	"sync"
	"time"
)

type Driver struct {
	mu      sync.Mutex
	bridges []*Bridge
}

func (d *Driver) SearchBridge(ctx context.Context, address string, _ bool) ([]models.Bridge, error) {
	if address == "" {
		ifaces, err := net.Interfaces()
		if err != nil {
			return nil, err
		}

		bridges := make([]models.Bridge, 0, len(ifaces))
		for _, iface := range ifaces {
			if iface.Name == "lo" {
				continue
			}

			addrs, err := iface.Addrs()
			if err != nil || len(addrs) == 0 {
				continue
			}

			for _, addr := range addrs {
				bridges = append(bridges, models.Bridge{
					ID:      -1,
					Name:    fmt.Sprintf("%s (%s)", iface.Name, addr),
					Driver:  models.DTLIFX,
					Address: addr.String(),
				})
			}
		}

		return bridges, nil
	}

	ctx2, cancel := context.WithCancel(ctx)
	defer cancel()
	_, err := createClient(ctx2, address, false)
	if err != nil {
		return nil, err
	}

	return []models.Bridge{{
		ID:      0,
		Name:    "Your network card",
		Driver:  models.DTLIFX,
		Address: address,
		Token:   "",
	}}, nil
}

func (d *Driver) SearchDevices(ctx context.Context, bridge models.Bridge, timeout time.Duration) ([]models.Device, error) {
	b, err := d.ensureBridge(ctx, bridge)
	if err != nil {
		return nil, err
	}

	before, err := d.ListDevices(ctx, bridge)
	if err != nil {
		return nil, err
	}

	err = b.StartSearch()
	if err != nil {
		return nil, err
	}

	if timeout < time.Second/10 {
		timeout = time.Second / 10
	}

	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	case <-time.After(timeout):
	}

	after, err := d.ListDevices(ctx, bridge)
	if err != nil {
		return nil, err
	}

	intersection := make([]models.Device, 0, 4)
	for _, device := range after {
		found := false
		for _, device2 := range before {
			if device2.InternalID == device.InternalID {
				found = true
				break
			}
		}

		if !found {
			intersection = append(intersection, device)
		}
	}

	return intersection, nil
}

func (d *Driver) ListDevices(ctx context.Context, bridge models.Bridge) ([]models.Device, error) {
	b, err := d.ensureBridge(ctx, bridge)
	if err != nil {
		return nil, err
	}

	return b.Devices(), nil
}

func (d *Driver) Publish(ctx context.Context, bridge models.Bridge, devices []models.Device) error {
	b, err := d.ensureBridge(ctx, bridge)
	if err != nil {
		return err
	}

	b.Publish(devices)

	return nil
}

func (d *Driver) Run(ctx context.Context, bridge models.Bridge, ch chan<- models.Event) error {
	b, err := d.ensureBridge(ctx, bridge)
	if err != nil {
		return err
	}

	return b.Run(ctx, bridge.Token == "debug")
}

func (d *Driver) ensureBridge(ctx context.Context, info models.Bridge) (*Bridge, error) {
	d.mu.Lock()
	for _, bridge := range d.bridges {
		if bridge.ip == info.Address {
			d.mu.Unlock()
			return bridge, nil
		}
	}
	d.mu.Unlock()

	bridge := &Bridge{
		ip:         info.Address,
		externalID: info.ID,
	}

	// Try to create a short-lived client to make sure it works.
	ctx2, cancel := context.WithCancel(ctx)
	defer cancel()
	_, err := createClient(ctx2, info.Address, info.Token == "debug")
	if err != nil {
		return nil, err
	}

	// To avoid a potential duplicate, try looking for it again before inserting
	d.mu.Lock()
	for _, bridge := range d.bridges {
		if bridge.ip == info.Address {
			d.mu.Unlock()
			return bridge, nil
		}
	}
	d.bridges = append(d.bridges, bridge)
	d.mu.Unlock()

	return bridge, nil
}