package dbstore

import (
	"context"
	"database/sql"
	"fmt"
	"os"

	"git.linuxforward.com/byop/byop-engine/models"
	_ "github.com/mattn/go-sqlite3" // Import SQLite driver for compatibility
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Store defines the interface for all database operations.
// This will include methods for all models (User, Client, App, Component, Deployment, Ticket, etc.)
type Store interface {
	// User methods
	CreateUser(ctx context.Context, user *models.User) error
	GetUserByEmail(ctx context.Context, email string) (*models.User, error)
	GetUserByID(ctx context.Context, id uint) (*models.User, error)
	GetUsers(ctx context.Context) ([]*models.User, error)
	UpdateUser(ctx context.Context, user *models.User) error
	DeleteUser(ctx context.Context, id uint) error
	CreateDefaultAdmin(ctx context.Context) error
	GetByUsername(ctx context.Context, username string) (*models.User, error)

	// Client methods
	CreateClient(ctx context.Context, client *models.Client) error
	GetAllClients(ctx context.Context) ([]*models.Client, error)
	GetClientByID(ctx context.Context, id uint) (*models.Client, error)
	UpdateClient(ctx context.Context, client *models.Client) error
	DeleteClient(ctx context.Context, id uint) error

	// App methods
	CreateApp(ctx context.Context, app *models.App) error
	GetAppByID(ctx context.Context, id uint) (*models.App, error)
	GetAppsByUserID(ctx context.Context, userID uint) ([]*models.App, error)
	UpdateApp(ctx context.Context, app *models.App) error
	DeleteApp(ctx context.Context, id uint) error
	UpdateAppStatus(ctx context.Context, appID uint, status string, message string) error
	UpdateAppPreview(ctx context.Context, appID uint, previewID uint, previewURL string) error
	GetAllApps(ctx context.Context) ([]*models.App, error)
	UpdateAppCurrentImage(ctx context.Context, appID uint, imageTag string, imageURI string) error

	// Component methods
	CreateComponent(ctx context.Context, component *models.Component) error
	GetComponentByID(ctx context.Context, id uint) (*models.Component, error)
	GetComponentsByUserID(ctx context.Context, userID uint) ([]*models.Component, error)
	GetAllComponents(ctx context.Context) ([]*models.Component, error)
	UpdateComponent(ctx context.Context, component *models.Component) error
	UpdateComponentStatus(ctx context.Context, componentID uint, status string, errorMsg string) error
	DeleteComponent(ctx context.Context, id uint) error

	// Deployment methods
	CreateDeployment(ctx context.Context, deployment *models.Deployment) error
	GetDeploymentByID(ctx context.Context, id uint) (*models.Deployment, error)
	GetDeploymentsByClientID(ctx context.Context, clientID uint) ([]*models.Deployment, error)
	GetAllDeployments(ctx context.Context) ([]*models.Deployment, error)
	UpdateDeployment(ctx context.Context, deployment *models.Deployment) error
	DeleteDeployment(ctx context.Context, id uint) error

	// Preview methods
	CreatePreview(ctx context.Context, preview *models.Preview) error
	GetPreviewByID(ctx context.Context, id uint) (*models.Preview, error)
	GetPreviewByAppID(ctx context.Context, appID uint) (*models.Preview, error)
	UpdatePreview(ctx context.Context, preview *models.Preview) error
	DeletePreview(ctx context.Context, id uint) error
	UpdatePreviewVPS(ctx context.Context, previewID uint, vpsID string, ipAddress string, previewURL string) error
	UpdatePreviewStatus(ctx context.Context, previewID uint, status string, errorMsg string) error
	UpdatePreviewBuildLogs(ctx context.Context, previewID uint, logs string) error
	UpdatePreviewDeployLogs(ctx context.Context, previewID uint, logs string) error
	GetAllPreviews(ctx context.Context) ([]*models.Preview, error)
	GetPreviewsByStatus(ctx context.Context, status string) ([]*models.Preview, error)
	GetPreviewsByAppID(ctx context.Context, appID uint) ([]*models.Preview, error)

	// Ticket methods
	CreateTicket(ctx context.Context, ticket *models.Ticket) error
	GetTicketByID(ctx context.Context, id uint) (*models.Ticket, error)
	GetTickets(ctx context.Context) ([]*models.Ticket, error)
	UpdateTicket(ctx context.Context, ticket *models.Ticket) error

	// TicketComment methods
	CreateTicketComment(ctx context.Context, comment *models.TicketComment) error
	GetTicketComments(ctx context.Context, ticketID uint) ([]*models.TicketComment, error)

	// BuildJob methods
	CreateBuildJob(ctx context.Context, job *models.BuildJob) error
	GetBuildJobByID(ctx context.Context, id uint) (*models.BuildJob, error)
	UpdateBuildJob(ctx context.Context, job *models.BuildJob) error
	UpdateBuildJobStatus(ctx context.Context, id uint, status models.BuildStatus, errorMessage string) error
	AppendBuildJobLog(ctx context.Context, id uint, logMessage string) error
	GetQueuedBuildJobs(ctx context.Context, limit int) ([]*models.BuildJob, error)
	GetBuildJobsByAppID(ctx context.Context, appID uint, page, pageSize int) ([]*models.BuildJob, int64, error)

	// General DB methods
	GetDB() *sql.DB
	GetGormDB() *gorm.DB
	Close() error
}

// SQLiteStore implements the Store interface for SQLite using GORM
type SQLiteStore struct {
	db    *gorm.DB
	rawDB *sql.DB // Keep for backward compatibility and specific raw SQL operations
	dsn   string
}

// NewSQLiteStore initializes a new SQLiteStore with GORM
func NewSQLiteStore(dataSourceName string) (*SQLiteStore, error) {
	// First check if the database file exists
	isNewDb := !fileExists(dataSourceName)
	if isNewDb {
		// Create the database file if it doesn't exist
		file, err := os.Create(dataSourceName)
		if err != nil {
			return nil, fmt.Errorf("failed to create SQLite database file: %w", err)
		}
		defer file.Close()
	}

	// Open GORM database
	gormDB, err := gorm.Open(sqlite.Open(dataSourceName), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent), // Change to logger.Info for debugging
	})
	if err != nil {
		return nil, fmt.Errorf("failed to open database with GORM: %w", err)
	}

	// Get underlying SQL DB for backward compatibility
	rawDB, err := gormDB.DB()
	if err != nil {
		return nil, fmt.Errorf("failed to get underlying SQL DB: %w", err)
	}

	if err := rawDB.Ping(); err != nil {
		return nil, fmt.Errorf("failed to ping database: %w", err)
	}

	// Enable foreign keys in SQLite
	err = gormDB.Exec("PRAGMA foreign_keys = ON").Error
	if err != nil {
		return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
	}

	// Auto-migrate all models
	err = gormDB.AutoMigrate(
		&models.User{},
		&models.Client{},
		&models.App{},
		&models.Component{},
		&models.Deployment{},
		&models.Preview{},
		&models.Ticket{},
		&models.TicketComment{},
		&models.Provider{},
		&models.BuildJob{},
	)
	if err != nil {
		return nil, fmt.Errorf("failed to auto-migrate models: %w", err)
	}

	store := &SQLiteStore{
		db:    gormDB,
		rawDB: rawDB,
		dsn:   dataSourceName,
	}

	return store, nil
}

// fileExists checks if a file exists
func fileExists(filename string) bool {
	_, err := os.Stat(filename)
	return !os.IsNotExist(err)
}

// GetDB returns the raw SQL database instance for backward compatibility
func (m *SQLiteStore) GetDB() *sql.DB {
	return m.rawDB
}

// GetGormDB returns the GORM database instance
func (m *SQLiteStore) GetGormDB() *gorm.DB {
	return m.db
}

// Connect establishes a connection to the SQLite database
func (m *SQLiteStore) Connect() error {
	// Connection is already established in NewSQLiteStore
	return nil
}

// Disconnect closes the connection to the SQLite database
func (m *SQLiteStore) Disconnect() error {
	sqlDB, err := m.db.DB()
	if err != nil {
		return fmt.Errorf("failed to get underlying SQL DB: %w", err)
	}
	return sqlDB.Close()
}

// Close provides a more standard name for closing the database connection.
// It simply calls Disconnect.
func (m *SQLiteStore) Close() error {
	return m.Disconnect()
}
