// Package infra gère l'infrastructure (serveurs, containers, nginx).
package infra

import (
	"database/sql"
	"fmt"
	"time"
)

// ============================================================================
// Servers
// ============================================================================

// CreateServer crée un nouveau serveur.
func (db *DB) CreateServer(s *Server) error {
	now := time.Now()
	result, err := db.Exec(`
		INSERT INTO servers (name, host, vpn_ip, ssh_port, ssh_user, ssh_key_file, has_incus, has_nginx, status, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		s.Name, s.Host, nullString(s.VpnIP), s.SSHPort, s.SSHUser, s.SSHKeyFile,
		boolToInt(s.HasIncus), boolToInt(s.HasNginx), s.Status, now, now)
	if err != nil {
		return fmt.Errorf("insert server: %w", err)
	}
	id, _ := result.LastInsertId()
	s.ID = id
	s.CreatedAt = now
	s.UpdatedAt = now
	return nil
}

// GetServer récupère un serveur par ID.
func (db *DB) GetServer(id int64) (*Server, error) {
	s := &Server{}
	err := db.QueryRow(`
		SELECT id, name, host, vpn_ip, ssh_port, ssh_user, ssh_key_file, has_incus, has_nginx, status, created_at, updated_at
		FROM servers WHERE id = ?`, id).Scan(
		&s.ID, &s.Name, &s.Host, &s.VpnIP, &s.SSHPort, &s.SSHUser, &s.SSHKeyFile,
		&s.HasIncus, &s.HasNginx, &s.Status, &s.CreatedAt, &s.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get server: %w", err)
	}
	return s, nil
}

// GetServerByName récupère un serveur par nom.
func (db *DB) GetServerByName(name string) (*Server, error) {
	s := &Server{}
	err := db.QueryRow(`
		SELECT id, name, host, vpn_ip, ssh_port, ssh_user, ssh_key_file, has_incus, has_nginx, status, created_at, updated_at
		FROM servers WHERE name = ?`, name).Scan(
		&s.ID, &s.Name, &s.Host, &s.VpnIP, &s.SSHPort, &s.SSHUser, &s.SSHKeyFile,
		&s.HasIncus, &s.HasNginx, &s.Status, &s.CreatedAt, &s.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get server by name: %w", err)
	}
	return s, nil
}

// ListServers retourne tous les serveurs.
func (db *DB) ListServers() ([]Server, error) {
	rows, err := db.Query(`
		SELECT id, name, host, vpn_ip, ssh_port, ssh_user, ssh_key_file, has_incus, has_nginx, status, created_at, updated_at
		FROM servers ORDER BY name`)
	if err != nil {
		return nil, fmt.Errorf("list servers: %w", err)
	}
	defer rows.Close()

	var servers []Server
	for rows.Next() {
		var s Server
		if err := rows.Scan(&s.ID, &s.Name, &s.Host, &s.VpnIP, &s.SSHPort, &s.SSHUser, &s.SSHKeyFile,
			&s.HasIncus, &s.HasNginx, &s.Status, &s.CreatedAt, &s.UpdatedAt); err != nil {
			return nil, fmt.Errorf("scan server: %w", err)
		}
		servers = append(servers, s)
	}
	return servers, nil
}

// UpdateServer met à jour un serveur.
func (db *DB) UpdateServer(s *Server) error {
	s.UpdatedAt = time.Now()
	_, err := db.Exec(`
		UPDATE servers SET name=?, host=?, vpn_ip=?, ssh_port=?, ssh_user=?, ssh_key_file=?,
		has_incus=?, has_nginx=?, status=?, updated_at=? WHERE id=?`,
		s.Name, s.Host, nullString(s.VpnIP), s.SSHPort, s.SSHUser, s.SSHKeyFile,
		boolToInt(s.HasIncus), boolToInt(s.HasNginx), s.Status, s.UpdatedAt, s.ID)
	if err != nil {
		return fmt.Errorf("update server: %w", err)
	}
	return nil
}

// UpdateServerStatus met à jour le statut d'un serveur.
func (db *DB) UpdateServerStatus(id int64, status ServerStatus) error {
	_, err := db.Exec(`UPDATE servers SET status=?, updated_at=? WHERE id=?`,
		status, time.Now(), id)
	if err != nil {
		return fmt.Errorf("update server status: %w", err)
	}
	return nil
}

// DeleteServer supprime un serveur.
func (db *DB) DeleteServer(id int64) error {
	_, err := db.Exec(`DELETE FROM servers WHERE id=?`, id)
	if err != nil {
		return fmt.Errorf("delete server: %w", err)
	}
	return nil
}

// ============================================================================
// Containers
// ============================================================================

// CreateContainer crée un nouveau container.
func (db *DB) CreateContainer(c *Container) error {
	now := time.Now()
	result, err := db.Exec(`
		INSERT INTO containers (server_id, name, incus_name, ip, vpn_ip, image, status, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		c.ServerID, c.Name, c.IncusName, nullString(c.IP), nullString(c.VpnIP), c.Image, c.Status, now, now)
	if err != nil {
		return fmt.Errorf("insert container: %w", err)
	}
	id, _ := result.LastInsertId()
	c.ID = id
	c.CreatedAt = now
	c.UpdatedAt = now
	return nil
}

// GetContainer récupère un container par ID.
func (db *DB) GetContainer(id int64) (*Container, error) {
	c := &Container{}
	err := db.QueryRow(`
		SELECT id, server_id, name, incus_name, ip, vpn_ip, image, status, created_at, updated_at
		FROM containers WHERE id = ?`, id).Scan(
		&c.ID, &c.ServerID, &c.Name, &c.IncusName, &c.IP, &c.VpnIP, &c.Image, &c.Status, &c.CreatedAt, &c.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get container: %w", err)
	}
	return c, nil
}

// ListContainersByServer retourne les containers d'un serveur.
func (db *DB) ListContainersByServer(serverID int64) ([]Container, error) {
	rows, err := db.Query(`
		SELECT id, server_id, name, incus_name, ip, vpn_ip, image, status, created_at, updated_at
		FROM containers WHERE server_id = ? ORDER BY name`, serverID)
	if err != nil {
		return nil, fmt.Errorf("list containers: %w", err)
	}
	defer rows.Close()

	var containers []Container
	for rows.Next() {
		var c Container
		if err := rows.Scan(&c.ID, &c.ServerID, &c.Name, &c.IncusName, &c.IP, &c.VpnIP, &c.Image, &c.Status, &c.CreatedAt, &c.UpdatedAt); err != nil {
			return nil, fmt.Errorf("scan container: %w", err)
		}
		containers = append(containers, c)
	}
	return containers, nil
}

// ListAllContainers retourne tous les containers.
func (db *DB) ListAllContainers() ([]Container, error) {
	rows, err := db.Query(`
		SELECT id, server_id, name, incus_name, ip, vpn_ip, image, status, created_at, updated_at
		FROM containers ORDER BY server_id, name`)
	if err != nil {
		return nil, fmt.Errorf("list all containers: %w", err)
	}
	defer rows.Close()

	var containers []Container
	for rows.Next() {
		var c Container
		if err := rows.Scan(&c.ID, &c.ServerID, &c.Name, &c.IncusName, &c.IP, &c.VpnIP, &c.Image, &c.Status, &c.CreatedAt, &c.UpdatedAt); err != nil {
			return nil, fmt.Errorf("scan container: %w", err)
		}
		containers = append(containers, c)
	}
	return containers, nil
}

// UpdateContainer met à jour un container.
func (db *DB) UpdateContainer(c *Container) error {
	c.UpdatedAt = time.Now()
	_, err := db.Exec(`
		UPDATE containers SET server_id=?, name=?, incus_name=?, ip=?, vpn_ip=?, image=?, status=?, updated_at=?
		WHERE id=?`,
		c.ServerID, c.Name, c.IncusName, nullString(c.IP), nullString(c.VpnIP), c.Image, c.Status, c.UpdatedAt, c.ID)
	if err != nil {
		return fmt.Errorf("update container: %w", err)
	}
	return nil
}

// UpdateContainerStatus met à jour le statut d'un container.
func (db *DB) UpdateContainerStatus(id int64, status ContainerStatus) error {
	_, err := db.Exec(`UPDATE containers SET status=?, updated_at=? WHERE id=?`,
		status, time.Now(), id)
	if err != nil {
		return fmt.Errorf("update container status: %w", err)
	}
	return nil
}

// DeleteContainer supprime un container.
func (db *DB) DeleteContainer(id int64) error {
	_, err := db.Exec(`DELETE FROM containers WHERE id=?`, id)
	if err != nil {
		return fmt.Errorf("delete container: %w", err)
	}
	return nil
}

// ============================================================================
// NginxConfigs
// ============================================================================

// CreateNginxConfig crée une nouvelle config Nginx.
func (db *DB) CreateNginxConfig(n *NginxConfig) error {
	now := time.Now()
	result, err := db.Exec(`
		INSERT INTO nginx_configs (server_id, domain, type, template, upstream, ssl_enabled, config_content, status, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		n.ServerID, n.Domain, n.Type, nullString(n.Template), nullString(n.Upstream),
		boolToInt(n.SSLEnabled), nullString(n.ConfigContent), n.Status, now, now)
	if err != nil {
		return fmt.Errorf("insert nginx config: %w", err)
	}
	id, _ := result.LastInsertId()
	n.ID = id
	n.CreatedAt = now
	n.UpdatedAt = now
	return nil
}

// GetNginxConfig récupère une config Nginx par ID.
func (db *DB) GetNginxConfig(id int64) (*NginxConfig, error) {
	n := &NginxConfig{}
	err := db.QueryRow(`
		SELECT id, server_id, domain, type, template, upstream, ssl_enabled, config_content, status, created_at, updated_at
		FROM nginx_configs WHERE id = ?`, id).Scan(
		&n.ID, &n.ServerID, &n.Domain, &n.Type, &n.Template, &n.Upstream,
		&n.SSLEnabled, &n.ConfigContent, &n.Status, &n.CreatedAt, &n.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get nginx config: %w", err)
	}
	return n, nil
}

// GetNginxConfigByDomain récupère une config par domaine.
func (db *DB) GetNginxConfigByDomain(serverID int64, domain string) (*NginxConfig, error) {
	n := &NginxConfig{}
	err := db.QueryRow(`
		SELECT id, server_id, domain, type, template, upstream, ssl_enabled, config_content, status, created_at, updated_at
		FROM nginx_configs WHERE server_id = ? AND domain = ?`, serverID, domain).Scan(
		&n.ID, &n.ServerID, &n.Domain, &n.Type, &n.Template, &n.Upstream,
		&n.SSLEnabled, &n.ConfigContent, &n.Status, &n.CreatedAt, &n.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get nginx config by domain: %w", err)
	}
	return n, nil
}

// ListNginxConfigsByServer retourne les configs d'un serveur.
func (db *DB) ListNginxConfigsByServer(serverID int64) ([]NginxConfig, error) {
	rows, err := db.Query(`
		SELECT id, server_id, domain, type, template, upstream, ssl_enabled, config_content, status, created_at, updated_at
		FROM nginx_configs WHERE server_id = ? ORDER BY domain`, serverID)
	if err != nil {
		return nil, fmt.Errorf("list nginx configs: %w", err)
	}
	defer rows.Close()

	var configs []NginxConfig
	for rows.Next() {
		var n NginxConfig
		if err := rows.Scan(&n.ID, &n.ServerID, &n.Domain, &n.Type, &n.Template, &n.Upstream,
			&n.SSLEnabled, &n.ConfigContent, &n.Status, &n.CreatedAt, &n.UpdatedAt); err != nil {
			return nil, fmt.Errorf("scan nginx config: %w", err)
		}
		configs = append(configs, n)
	}
	return configs, nil
}

// ListAllNginxConfigs retourne toutes les configs.
func (db *DB) ListAllNginxConfigs() ([]NginxConfig, error) {
	rows, err := db.Query(`
		SELECT id, server_id, domain, type, template, upstream, ssl_enabled, config_content, status, created_at, updated_at
		FROM nginx_configs ORDER BY server_id, domain`)
	if err != nil {
		return nil, fmt.Errorf("list all nginx configs: %w", err)
	}
	defer rows.Close()

	var configs []NginxConfig
	for rows.Next() {
		var n NginxConfig
		if err := rows.Scan(&n.ID, &n.ServerID, &n.Domain, &n.Type, &n.Template, &n.Upstream,
			&n.SSLEnabled, &n.ConfigContent, &n.Status, &n.CreatedAt, &n.UpdatedAt); err != nil {
			return nil, fmt.Errorf("scan nginx config: %w", err)
		}
		configs = append(configs, n)
	}
	return configs, nil
}

// UpdateNginxConfig met à jour une config.
func (db *DB) UpdateNginxConfig(n *NginxConfig) error {
	n.UpdatedAt = time.Now()
	_, err := db.Exec(`
		UPDATE nginx_configs SET server_id=?, domain=?, type=?, template=?, upstream=?,
		ssl_enabled=?, config_content=?, status=?, updated_at=? WHERE id=?`,
		n.ServerID, n.Domain, n.Type, nullString(n.Template), nullString(n.Upstream),
		boolToInt(n.SSLEnabled), nullString(n.ConfigContent), n.Status, n.UpdatedAt, n.ID)
	if err != nil {
		return fmt.Errorf("update nginx config: %w", err)
	}
	return nil
}

// DeleteNginxConfig supprime une config.
func (db *DB) DeleteNginxConfig(id int64) error {
	_, err := db.Exec(`DELETE FROM nginx_configs WHERE id=?`, id)
	if err != nil {
		return fmt.Errorf("delete nginx config: %w", err)
	}
	return nil
}

// ============================================================================
// AppBindings
// ============================================================================

// CreateAppBinding crée un nouveau binding.
func (db *DB) CreateAppBinding(b *AppBinding) error {
	now := time.Now()
	result, err := db.Exec(`
		INSERT INTO app_bindings (app_id, container_id, nginx_config_id, server_id, type, created_at)
		VALUES (?, ?, ?, ?, ?, ?)`,
		b.AppID, nullInt64(b.ContainerID), nullInt64(b.NginxConfigID), nullInt64(b.ServerID), b.Type, now)
	if err != nil {
		return fmt.Errorf("insert app binding: %w", err)
	}
	id, _ := result.LastInsertId()
	b.ID = id
	b.CreatedAt = now
	return nil
}

// ListAppBindings retourne les bindings d'une app.
func (db *DB) ListAppBindings(appID string) ([]AppBinding, error) {
	rows, err := db.Query(`
		SELECT id, app_id, container_id, nginx_config_id, server_id, type, created_at
		FROM app_bindings WHERE app_id = ?`, appID)
	if err != nil {
		return nil, fmt.Errorf("list app bindings: %w", err)
	}
	defer rows.Close()

	var bindings []AppBinding
	for rows.Next() {
		var b AppBinding
		if err := rows.Scan(&b.ID, &b.AppID, &b.ContainerID, &b.NginxConfigID, &b.ServerID, &b.Type, &b.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan app binding: %w", err)
		}
		bindings = append(bindings, b)
	}
	return bindings, nil
}

// DeleteAppBinding supprime un binding.
func (db *DB) DeleteAppBinding(id int64) error {
	_, err := db.Exec(`DELETE FROM app_bindings WHERE id=?`, id)
	if err != nil {
		return fmt.Errorf("delete app binding: %w", err)
	}
	return nil
}

// DeleteAppBindingsByApp supprime tous les bindings d'une app.
func (db *DB) DeleteAppBindingsByApp(appID string) error {
	_, err := db.Exec(`DELETE FROM app_bindings WHERE app_id=?`, appID)
	if err != nil {
		return fmt.Errorf("delete app bindings: %w", err)
	}
	return nil
}

// ============================================================================
// Helpers
// ============================================================================

func nullString(s string) interface{} {
	if s == "" {
		return nil
	}
	return s
}

func nullInt64(i *int64) interface{} {
	if i == nil {
		return nil
	}
	return *i
}

func boolToInt(b bool) int {
	if b {
		return 1
	}
	return 0
}

// ============================================================================
// Aggregate queries
// ============================================================================

// GetInfraOverview retourne une vue complète de l'infrastructure.
func (db *DB) GetInfraOverview() (*InfraOverview, error) {
	servers, err := db.ListServers()
	if err != nil {
		return nil, err
	}

	overview := &InfraOverview{
		Servers: make([]ServerWithContainers, len(servers)),
	}

	for i, s := range servers {
		overview.Servers[i].Server = s
		containers, err := db.ListContainersByServer(s.ID)
		if err != nil {
			return nil, err
		}
		overview.Servers[i].Containers = containers
	}

	overview.NginxConfigs, err = db.ListAllNginxConfigs()
	if err != nil {
		return nil, err
	}

	return overview, nil
}
