// internal/api/bot_service.go
package api

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/nbd-wtf/go-nostr" // required for key derivation
	"git.sovbit.dev/Enki/nostr-poster/internal/crypto"
	"git.sovbit.dev/Enki/nostr-poster/internal/db"
	"git.sovbit.dev/Enki/nostr-poster/internal/models"
	"git.sovbit.dev/Enki/nostr-poster/internal/nostr/events"
	"git.sovbit.dev/Enki/nostr-poster/internal/nostr/relay"
	"go.uber.org/zap"
)

// BotService provides functionality for managing bots
type BotService struct {
	db       *db.DB
	keyStore *crypto.KeyStore
	eventMgr *events.EventManager
	relayMgr *relay.Manager
	logger   *zap.Logger
}

// NewBotService creates a new BotService
func NewBotService(
	db *db.DB,
	keyStore *crypto.KeyStore,
	eventMgr *events.EventManager,
	relayMgr *relay.Manager,
	logger *zap.Logger,
) *BotService {
	return &BotService{
		db:       db,
		keyStore: keyStore,
		eventMgr: eventMgr,
		relayMgr: relayMgr,
		logger:   logger,
	}
}

// GetPrivateKey returns the private key for the given pubkey from the keystore.
func (s *BotService) GetPrivateKey(pubkey string) (string, error) {
	return s.keyStore.GetPrivateKey(pubkey)
}

// ListUserBots lists all bots owned by a user
func (s *BotService) ListUserBots(ownerPubkey string) ([]*models.Bot, error) {
	query := `
	SELECT b.* FROM bots b
	WHERE b.owner_pubkey = ?
	ORDER BY b.created_at DESC
	`

	var bots []*models.Bot
	err := s.db.Select(&bots, query, ownerPubkey)
	if err != nil {
		return nil, fmt.Errorf("failed to list bots: %w", err)
	}

	// Load associated data for each bot
	for _, bot := range bots {
		if err := s.loadBotRelatedData(bot); err != nil {
			s.logger.Warn("Failed to load related data for bot",
				zap.Int64("botID", bot.ID),
				zap.Error(err))
		}
	}

	return bots, nil
}

// GetBotByID gets a bot by ID and verifies ownership
func (s *BotService) GetBotByID(botID int64, ownerPubkey string) (*models.Bot, error) {
	query := `
	SELECT b.* FROM bots b
	WHERE b.id = ? AND b.owner_pubkey = ?
	`

	var bot models.Bot
	err := s.db.Get(&bot, query, botID, ownerPubkey)
	if err != nil {
		return nil, fmt.Errorf("failed to get bot: %w", err)
	}

	// Load associated data
	if err := s.loadBotRelatedData(&bot); err != nil {
		return &bot, fmt.Errorf("failed to load related data: %w", err)
	}

	return &bot, nil
}

// CreateBot creates a new bot
func (s *BotService) CreateBot(bot *models.Bot) (*models.Bot, error) {
	// Start a transaction
	tx, err := s.db.Beginx()
	if err != nil {
		s.logger.Error("Failed to start transaction", zap.Error(err))
		return nil, fmt.Errorf("failed to start transaction: %w", err)
	}
	defer tx.Rollback()

	s.logger.Info("Creating new bot",
		zap.String("name", bot.Name),
		zap.String("privkey_type", detectKeyType(bot.EncryptedPrivkey)))

	// Check if we need to generate a keypair
	if bot.Pubkey == "" && bot.EncryptedPrivkey == "" {
		// Generate a new keypair
		s.logger.Info("Generating new keypair")
		pubkey, privkey, err := s.keyStore.GenerateKey()
		if err != nil {
			s.logger.Error("Failed to generate keypair", zap.Error(err))
			return nil, fmt.Errorf("failed to generate keypair: %w", err)
		}

		bot.Pubkey = pubkey
		bot.EncryptedPrivkey = privkey // This will be encrypted by the KeyStore
		s.logger.Info("Generated keypair successfully", zap.String("pubkey", pubkey))
	} else if bot.EncryptedPrivkey != "" {
		// If only privkey is provided, derive the pubkey
		if bot.Pubkey == "" {
			privkey := bot.EncryptedPrivkey
			s.logger.Info("Deriving pubkey from provided privkey",
				zap.String("key_type", detectKeyType(privkey)))

			if len(privkey) > 4 && privkey[:4] == "nsec" {
				s.logger.Debug("Decoding nsec key")
				var decodeErr error
				privkey, decodeErr = s.keyStore.DecodeNsecKey(privkey)
				if decodeErr != nil {
					s.logger.Error("Failed to decode nsec key", zap.Error(decodeErr))
					return nil, fmt.Errorf("failed to decode nsec key: %w", decodeErr)
				}
				s.logger.Debug("Successfully decoded nsec key to hex format")
			}

			derivedPub, err := nostr.GetPublicKey(privkey)
			if err != nil {
				s.logger.Error("Invalid private key", zap.Error(err))
				return nil, fmt.Errorf("invalid private key: %w", err)
			}
			bot.Pubkey = derivedPub
			s.logger.Info("Successfully derived pubkey", zap.String("pubkey", derivedPub))
		}

		// Import the provided keypair
		s.logger.Debug("Importing keypair to keystore")
		if err := s.keyStore.AddKey(bot.Pubkey, bot.EncryptedPrivkey); err != nil {
			s.logger.Error("Failed to import keypair", zap.Error(err))
			return nil, fmt.Errorf("failed to import keypair: %w", err)
		}
		s.logger.Debug("Successfully imported keypair to keystore")
	} else {
		s.logger.Error("Missing key information",
			zap.Bool("has_pubkey", bot.Pubkey != ""),
			zap.Bool("has_privkey", bot.EncryptedPrivkey != ""))
		return nil, errors.New("either provide both pubkey and privkey or none for auto-generation")
	}

	// Set created time
	bot.CreatedAt = time.Now()

	// Insert the bot
	query := `
	INSERT INTO bots (
		pubkey, encrypted_privkey, name, display_name, bio, nip05, zap_address,
		profile_picture, banner, created_at, owner_pubkey
	) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`

	result, err := tx.Exec(
		query,
		bot.Pubkey, bot.EncryptedPrivkey, bot.Name, bot.DisplayName, bot.Bio,
		bot.Nip05, bot.ZapAddress, bot.ProfilePicture, bot.Banner,
		bot.CreatedAt, bot.OwnerPubkey,
	)
	if err != nil {
		s.logger.Error("Failed to insert bot", zap.Error(err))
		return nil, fmt.Errorf("failed to insert bot: %w", err)
	}

	// Get the inserted ID
	botID, err := result.LastInsertId()
	if err != nil {
		s.logger.Error("Failed to get inserted ID", zap.Error(err))
		return nil, fmt.Errorf("failed to get inserted ID: %w", err)
	}
	bot.ID = botID

	// Create default post config
	postConfig := &models.PostConfig{
		BotID:           botID,
		Hashtags:        "[]",
		IntervalMinutes: 60,
		PostTemplate:    "",
		Enabled:         false,
	}

	postConfigQuery := `
	INSERT INTO post_config (
		bot_id, hashtags, interval_minutes, post_template, enabled
	) VALUES (?, ?, ?, ?, ?)
	`

	_, err = tx.Exec(
		postConfigQuery,
		postConfig.BotID, postConfig.Hashtags, postConfig.IntervalMinutes,
		postConfig.PostTemplate, postConfig.Enabled,
	)
	if err != nil {
		s.logger.Error("Failed to insert post config", zap.Error(err))
		return nil, fmt.Errorf("failed to insert post config: %w", err)
	}

	// Create default media config
	mediaConfig := &models.MediaConfig{
		BotID:            botID,
		PrimaryService:   "nip94",
		FallbackService:  "blossom",
		Nip94ServerURL:   "",
		BlossomServerURL: "",
	}

	mediaConfigQuery := `
	INSERT INTO media_config (
		bot_id, primary_service, fallback_service, nip94_server_url, blossom_server_url
	) VALUES (?, ?, ?, ?, ?)
	`

	_, err = tx.Exec(
		mediaConfigQuery,
		mediaConfig.BotID, mediaConfig.PrimaryService, mediaConfig.FallbackService,
		mediaConfig.Nip94ServerURL, mediaConfig.BlossomServerURL,
	)
	if err != nil {
		s.logger.Error("Failed to insert media config", zap.Error(err))
		return nil, fmt.Errorf("failed to insert media config: %w", err)
	}

	// Add default relays
	defaultRelays := []struct {
		URL   string
		Read  bool
		Write bool
	}{
		{"wss://relay.damus.io", true, true},
		{"wss://nostr.mutinywallet.com", true, true},
		{"wss://relay.nostr.band", true, true},
	}

	relayQuery := `
	INSERT INTO relays (bot_id, url, read, write)
	VALUES (?, ?, ?, ?)
	`

	for _, relay := range defaultRelays {
		_, err = tx.Exec(relayQuery, botID, relay.URL, relay.Read, relay.Write)
		if err != nil {
			s.logger.Error("Failed to insert relay",
				zap.String("url", relay.URL),
				zap.Error(err))
			return nil, fmt.Errorf("failed to insert relay: %w", err)
		}
	}

	// Commit the transaction
	if err := tx.Commit(); err != nil {
		s.logger.Error("Failed to commit transaction", zap.Error(err))
		return nil, fmt.Errorf("failed to commit transaction: %w", err)
	}

	// Load associated data
	bot.PostConfig = postConfig
	bot.MediaConfig = mediaConfig
	bot.Relays = []*models.Relay{
		{BotID: botID, URL: "wss://relay.damus.io", Read: true, Write: true},
		{BotID: botID, URL: "wss://nostr.mutinywallet.com", Read: true, Write: true},
		{BotID: botID, URL: "wss://relay.nostr.band", Read: true, Write: true},
	}

	s.logger.Info("Bot created successfully",
		zap.Int64("id", botID),
		zap.String("name", bot.Name),
		zap.String("pubkey", bot.Pubkey))

	return bot, nil
}

// UpdateBot updates an existing bot
func (s *BotService) UpdateBot(bot *models.Bot) (*models.Bot, error) {
	// Check if the bot exists and belongs to the owner
	_, err := s.GetBotByID(bot.ID, bot.OwnerPubkey)
	if err != nil {
		return nil, fmt.Errorf("bot not found or not owned by user: %w", err)
	}

	// We don't update the pubkey or encrypted_privkey
	query := `
	UPDATE bots SET
		name = ?,
		display_name = ?,
		bio = ?,
		nip05 = ?,
		zap_address = ?,
		profile_picture = ?,
		banner = ?
	WHERE id = ? AND owner_pubkey = ?
	`

	_, err = s.db.Exec(
		query,
		bot.Name, bot.DisplayName, bot.Bio, bot.Nip05,
		bot.ZapAddress, bot.ProfilePicture, bot.Banner,
		bot.ID, bot.OwnerPubkey,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to update bot: %w", err)
	}

	// Get the updated bot
	updatedBot, err := s.GetBotByID(bot.ID, bot.OwnerPubkey)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve updated bot: %w", err)
	}

	return updatedBot, nil
}

// DeleteBot deletes a bot
func (s *BotService) DeleteBot(botID int64, ownerPubkey string) error {
	// Check if the bot exists and belongs to the owner
	_, err := s.GetBotByID(botID, ownerPubkey)
	if err != nil {
		return fmt.Errorf("bot not found or not owned by user: %w", err)
	}

	// Start a transaction
	tx, err := s.db.Beginx()
	if err != nil {
		return fmt.Errorf("failed to start transaction: %w", err)
	}
	defer tx.Rollback()

	// Delete the bot (cascade will handle related tables)
	query := `DELETE FROM bots WHERE id = ? AND owner_pubkey = ?`
	_, err = tx.Exec(query, botID, ownerPubkey)
	if err != nil {
		return fmt.Errorf("failed to delete bot: %w", err)
	}

	// Commit the transaction
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit transaction: %w", err)
	}

	return nil
}

// UpdateBotConfig updates a bot's configuration
func (s *BotService) UpdateBotConfig(
	botID int64,
	ownerPubkey string,
	postConfig *models.PostConfig,
	mediaConfig *models.MediaConfig,
) error {
	// Check if the bot exists and belongs to the owner
	_, err := s.GetBotByID(botID, ownerPubkey)
	if err != nil {
		return fmt.Errorf("bot not found or not owned by user: %w", err)
	}

	// Start a transaction
	tx, err := s.db.Beginx()
	if err != nil {
		return fmt.Errorf("failed to start transaction: %w", err)
	}
	defer tx.Rollback()

	// Update post config if provided
	if postConfig != nil {
		query := `
		UPDATE post_config SET
			hashtags = ?,
			interval_minutes = ?,
			post_template = ?,
			enabled = ?
		WHERE bot_id = ?
		`

		_, err = tx.Exec(
			query,
			postConfig.Hashtags, postConfig.IntervalMinutes,
			postConfig.PostTemplate, postConfig.Enabled,
			botID,
		)
		if err != nil {
			return fmt.Errorf("failed to update post config: %w", err)
		}
	}

	// Update media config if provided
	if mediaConfig != nil {
		query := `
		UPDATE media_config SET
			primary_service = ?,
			fallback_service = ?,
			nip94_server_url = ?,
			blossom_server_url = ?
		WHERE bot_id = ?
		`

		_, err = tx.Exec(
			query,
			mediaConfig.PrimaryService, mediaConfig.FallbackService,
			mediaConfig.Nip94ServerURL, mediaConfig.BlossomServerURL,
			botID,
		)
		if err != nil {
			return fmt.Errorf("failed to update media config: %w", err)
		}
	}

	// Commit the transaction
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit transaction: %w", err)
	}

	return nil
}

// GetBotRelays gets the relays for a bot
func (s *BotService) GetBotRelays(botID int64, ownerPubkey string) ([]*models.Relay, error) {
	// Check if the bot exists and belongs to the owner
	_, err := s.GetBotByID(botID, ownerPubkey)
	if err != nil {
		return nil, fmt.Errorf("bot not found or not owned by user: %w", err)
	}

	// Get the relays
	query := `
	SELECT id, bot_id, url, read, write
	FROM relays
	WHERE bot_id = ?
	ORDER BY id
	`

	var relays []*models.Relay
	err = s.db.Select(&relays, query, botID)
	if err != nil {
		return nil, fmt.Errorf("failed to get relays: %w", err)
	}

	return relays, nil
}

// UpdateBotRelays updates the relays for a bot
func (s *BotService) UpdateBotRelays(botID int64, ownerPubkey string, relays []*models.Relay) error {
	// Check if the bot exists and belongs to the owner
	_, err := s.GetBotByID(botID, ownerPubkey)
	if err != nil {
		return fmt.Errorf("bot not found or not owned by user: %w", err)
	}

	// Start a transaction
	tx, err := s.db.Beginx()
	if err != nil {
		return fmt.Errorf("failed to start transaction: %w", err)
	}
	defer tx.Rollback()

	// Delete existing relays
	_, err = tx.Exec("DELETE FROM relays WHERE bot_id = ?", botID)
	if err != nil {
		return fmt.Errorf("failed to delete existing relays: %w", err)
	}

	// Insert new relays
	for _, relay := range relays {
		_, err = tx.Exec(
			"INSERT INTO relays (bot_id, url, read, write) VALUES (?, ?, ?, ?)",
			botID, relay.URL, relay.Read, relay.Write,
		)
		if err != nil {
			return fmt.Errorf("failed to insert relay: %w", err)
		}
	}

	// Commit the transaction
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit transaction: %w", err)
	}

	return nil
}

// PublishBotProfile publishes a bot's profile to Nostr
func (s *BotService) PublishBotProfile(botID int64, ownerPubkey string) error {
	// Get the bot
	bot, err := s.GetBotByID(botID, ownerPubkey)
	if err != nil {
		return fmt.Errorf("bot not found or not owned by user: %w", err)
	}

	// Create and sign the metadata event
	event, err := s.eventMgr.CreateAndSignMetadataEvent(bot)
	if err != nil {
		return fmt.Errorf("failed to create metadata event: %w", err)
	}

	// Set up relay connections
	for _, relay := range bot.Relays {
		if relay.Write {
			if err := s.relayMgr.AddRelay(relay.URL, relay.Read, relay.Write); err != nil {
				s.logger.Warn("Failed to add relay",
					zap.String("url", relay.URL),
					zap.Error(err))
			}
		}
	}

	// Publish the event
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	published, err := s.relayMgr.PublishEvent(ctx, event)
	if err != nil {
		return fmt.Errorf("failed to publish profile: %w", err)
	}

	s.logger.Info("Published profile to relays",
		zap.Int64("botID", botID),
		zap.Strings("relays", published))

	return nil
}

// Helper function to load related data for a bot
func (s *BotService) loadBotRelatedData(bot *models.Bot) error {
	// Load post config
	var postConfig models.PostConfig
	err := s.db.Get(&postConfig, "SELECT * FROM post_config WHERE bot_id = ?", bot.ID)
	if err != nil {
		return fmt.Errorf("failed to load post config: %w", err)
	}
	bot.PostConfig = &postConfig

	// Load media config
	var mediaConfig models.MediaConfig
	err = s.db.Get(&mediaConfig, "SELECT * FROM media_config WHERE bot_id = ?", bot.ID)
	if err != nil {
		return fmt.Errorf("failed to load media config: %w", err)
	}
	bot.MediaConfig = &mediaConfig

	// Load relays
	var relays []*models.Relay
	err = s.db.Select(&relays, "SELECT * FROM relays WHERE bot_id = ?", bot.ID)
	if err != nil {
		return fmt.Errorf("failed to load relays: %w", err)
	}
	bot.Relays = relays

	return nil
}

// Helper function to detect key type for logging
func detectKeyType(key string) string {
	if key == "" {
		return "empty"
	}
	if len(key) > 4 {
		if key[:4] == "nsec" {
			return "nsec"
		} else if key[:4] == "npub" {
			return "npub"
		}
	}
	if len(key) == 64 {
		return "hex"
	}
	return "unknown"
}
