package peer

import (
	"context"
	"crypto/ed25519"
	"encoding/base64"
	"fmt"
	"log/slog"
	"net"
	"os"
	"strings"

	"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
	"koti.casa/numenor-labs/dsfx/internal/lib/disk"
	"koti.casa/numenor-labs/dsfx/internal/lib/logging"
	"koti.casa/numenor-labs/dsfx/internal/lib/network"
	"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
	"koti.casa/numenor-labs/dsfx/internal/lib/system"
)

const (
	// DefaultConfigDir is the default directory for the dsfx configuration.
	DefaultConfigDir = "/etc/dsfx/config"
	// DefaultStorageDir is the default directory for the dsfx storage.
	DefaultStorageDir = "/etc/dsfx/data"
	// DefaultHost is the default host for the dsfxctl application.
	DefaultHost = "0.0.0.0"
	// DefaultPort is the default port for the dsfxctl application.
	DefaultPort = "8000"
)

// Conf holds the configuration for the dsfxctl application.
type Conf struct {
	// Directories
	ConfigDir  string
	StorageDir string
	// Networking
	Host string
	Port string
}

func loadConfigFromSystem(sys system.System) Conf {
	var c Conf

	c.ConfigDir = sys.GetEnv("DSFX_CONFIG_DIR")
	if c.ConfigDir == "" {
		c.ConfigDir = DefaultConfigDir
	}

	c.StorageDir = sys.GetEnv("DSFX_STORAGE_DIR")
	if c.StorageDir == "" {
		c.StorageDir = DefaultStorageDir
	}

	c.Host = sys.GetEnv("DSFX_HOST")
	if c.Host == "" {
		c.Host = DefaultHost
	}

	c.Port = sys.GetEnv("DSFX_PORT")
	if c.Port == "" {
		c.Port = DefaultPort
	}

	return c
}

type Peer struct {
	disk    disk.Disk
	system  system.System
	config  scoped.StorageScope
	storage scoped.StorageScope
	conf    Conf
}

func New(disk disk.Disk, system system.System) *Peer {
	conf := loadConfigFromSystem(system)

	config := scoped.New(disk, conf.ConfigDir)
	storage := scoped.New(disk, conf.StorageDir)

	return &Peer{disk, system, config, storage, conf}
}

func (p *Peer) Run(ctx context.Context) error {
	opts := &slog.HandlerOptions{
		AddSource: false,
		Level:     slog.LevelDebug,
	}
	logger := slog.New(slog.NewTextHandler(p.system.Stdout(), opts))

	slog.SetDefault(logger)
	ctx = logging.WithContext(ctx, logger)

	err := p.disk.MkdirAll(p.conf.ConfigDir, 0755)
	if err != nil {
		logger.ErrorContext(ctx, "failed to create config dir", slog.Any("error", err))
		return err
	}

	err = p.disk.MkdirAll(p.conf.StorageDir, 0755)
	if err != nil {
		logger.ErrorContext(ctx, "failed to create storage dir", slog.Any("error", err))
		return err
	}

	id, err := p.loadIdentity()
	if err != nil {
		logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
		return err
	}

	admins, err := p.loadAdmins()
	if err != nil {
		logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err))
		return err
	}

	if len(admins) == 0 {
		logger.WarnContext(ctx, "no admins found", slog.String("admins", "none"))
	}

	tcpAddrRaw := net.JoinHostPort(p.conf.Host, p.conf.Port)
	tcpAddr, err := net.ResolveTCPAddr("tcp", tcpAddrRaw)
	if err != nil {
		return err
	}
	addr := network.FromTCPAddr(tcpAddr, id.Public().(ed25519.PublicKey))

	// ---------------------------------------------------------------------------
	// Listener

	listener, err := network.Listen(ctx, id, addr)
	if err != nil {
		logger.ErrorContext(ctx, "listener error", slog.Any("error", err))
		return err
	}

	logger.InfoContext(ctx, "serving", slog.String("address", addr.String()))

	for {
		conn, err := listener.Accept()
		if err != nil {
			logger.ErrorContext(ctx, "accept failure", slog.Any("error", err))
			continue
		}

		go p.handleConnection(ctx, conn)
	}
}

// loadAdmins ...
func (p *Peer) loadAdmins() ([]string, error) {
	hasKeyFile, err := p.hasAdminsFile()
	if err != nil {
		return nil, fmt.Errorf("failed to check for admins file: %w", err)
	}

	if !hasKeyFile {
		if err := p.createAdminsFile(); err != nil {
			return nil, fmt.Errorf("failed to create admins file: %w", err)
		}
	}

	return p.readAdminsFile()
}

// hasAdminsFile ...
func (p *Peer) hasAdminsFile() (bool, error) {
	f, err := p.config.Open("admins")
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	defer f.Close()

	return true, nil
}

// createAdminsFile ...
func (p *Peer) createAdminsFile() error {
	f, err := p.config.Create("admins")
	if err != nil {
		return err
	}
	defer f.Close()

	return nil
}

// readAdminsFile ...
func (p *Peer) readAdminsFile() ([]string, error) {
	f, err := p.config.Open("admins")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	keyRaw := make([]byte, 0)
	_, err = f.Read(keyRaw)
	if err != nil {
		return nil, err
	}

	lines := strings.SplitSeq(string(keyRaw), "\n")

	nonEmptyLines := []string{}
	for line := range lines {
		if strings.TrimSpace(line) != "" {
			nonEmptyLines = append(nonEmptyLines, line)
		}
	}

	return nonEmptyLines, nil
}

// loadIdentity loads the private key from the key file. If the key file does not
// exist, it creates a new key file with a generated private key and returns it.
func (p *Peer) loadIdentity() (ed25519.PrivateKey, error) {
	hasKeyFile, err := p.hasKeyFile()
	if err != nil {
		return nil, err
	}

	if hasKeyFile {
		return p.readKeyFile()
	}

	if err := p.createKeyFile(); err != nil {
		return nil, err
	}

	return p.readKeyFile()
}

// hasKeyFile checks if the key file exists in the disk storage.
func (p *Peer) hasKeyFile() (bool, error) {
	f, err := p.config.Open("key")
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	defer f.Close()

	return true, nil
}

// createKeyFile creates a new key file with a generated private key.
func (p *Peer) createKeyFile() error {
	f, err := p.config.Create("key")
	if err != nil {
		return err
	}
	defer f.Close()

	privkey, err := identity.Generate()
	if err != nil {
		return err
	}

	_, err = f.Write([]byte(base64.StdEncoding.EncodeToString(privkey)))
	if err != nil {
		return err
	}

	return nil
}

// readKeyFile reads the private key from the key file and returns it as an ed25519.PrivateKey.
func (p *Peer) readKeyFile() (ed25519.PrivateKey, error) {
	f, err := p.config.Open("key")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	keyRaw := make([]byte, ed25519.PrivateKeySize)
	n, err := f.Read(keyRaw)
	if err != nil {
		return nil, err
	}
	if n != ed25519.PrivateKeySize {
		return nil, fmt.Errorf("key file is not the correct size: %d", n)
	}

	return ed25519.PrivateKey(keyRaw), nil
}

func (p *Peer) handleConnection(ctx context.Context, conn net.Conn) error {
	defer conn.Close()

	logger := logging.FromContext(ctx)

	msg := make([]byte, 1024)
	n, err := conn.Read(msg)
	if err != nil {
		logger.ErrorContext(ctx, "failed to read from connection", slog.Any("error", err))
		return err
	}

	logger.InfoContext(ctx, "received msg", slog.Int("bytes", n))

	return nil
}