package providers

import (
	"context"
	"database/sql"
	"fmt"
	"sync"
	"time"

	_ "github.com/glebarez/sqlite" // Pure Go SQLite driver
	"go.mongodb.org/mongo-driver/mongo"

	"github.com/bitechdev/ResolveSpec/pkg/logger"
)

// SQLiteProvider implements Provider for SQLite databases
type SQLiteProvider struct {
	db        *sql.DB
	dbMu      sync.RWMutex
	dbFactory func() (*sql.DB, error)
	config    ConnectionConfig
}

// NewSQLiteProvider creates a new SQLite provider
func NewSQLiteProvider() *SQLiteProvider {
	return &SQLiteProvider{}
}

// Connect establishes a SQLite connection
func (p *SQLiteProvider) Connect(ctx context.Context, cfg ConnectionConfig) error {
	// Build DSN
	dsn, err := cfg.BuildDSN()
	if err != nil {
		return fmt.Errorf("failed to build DSN: %w", err)
	}

	// Open database connection
	db, err := sql.Open("sqlite", dsn)
	if err != nil {
		return fmt.Errorf("failed to open SQLite connection: %w", err)
	}

	// Test the connection with context timeout
	connectCtx, cancel := context.WithTimeout(ctx, cfg.GetConnectTimeout())
	err = db.PingContext(connectCtx)
	cancel()

	if err != nil {
		db.Close()
		return fmt.Errorf("failed to ping SQLite database: %w", err)
	}

	// Configure connection pool
	// Note: SQLite works best with MaxOpenConns=1 for write operations
	// but can handle multiple readers
	if cfg.GetMaxOpenConns() != nil {
		db.SetMaxOpenConns(*cfg.GetMaxOpenConns())
	} else {
		// Default to 1 for SQLite to avoid "database is locked" errors
		db.SetMaxOpenConns(1)
	}

	if cfg.GetMaxIdleConns() != nil {
		db.SetMaxIdleConns(*cfg.GetMaxIdleConns())
	}
	if cfg.GetConnMaxLifetime() != nil {
		db.SetConnMaxLifetime(*cfg.GetConnMaxLifetime())
	}
	if cfg.GetConnMaxIdleTime() != nil {
		db.SetConnMaxIdleTime(*cfg.GetConnMaxIdleTime())
	}

	// Enable WAL mode for better concurrent access
	_, err = db.ExecContext(ctx, "PRAGMA journal_mode=WAL")
	if err != nil {
		if cfg.GetEnableLogging() {
			logger.Warn("Failed to enable WAL mode for SQLite", "error", err)
		}
		// Don't fail connection if WAL mode cannot be enabled
	}

	// Set busy timeout to handle locked database (minimum 2 minutes = 120000ms)
	busyTimeout := cfg.GetQueryTimeout().Milliseconds()
	if busyTimeout < 120000 {
		busyTimeout = 120000 // Enforce minimum of 2 minutes
	}
	_, err = db.ExecContext(ctx, fmt.Sprintf("PRAGMA busy_timeout=%d", busyTimeout))
	if err != nil {
		if cfg.GetEnableLogging() {
			logger.Warn("Failed to set busy timeout for SQLite", "error", err)
		}
	}

	p.db = db
	p.config = cfg

	if cfg.GetEnableLogging() {
		logger.Info("SQLite connection established: name=%s, filepath=%s", cfg.GetName(), cfg.GetFilePath())
	}

	return nil
}

// Close closes the SQLite connection
func (p *SQLiteProvider) Close() error {
	if p.db == nil {
		return nil
	}

	err := p.db.Close()
	if err != nil {
		return fmt.Errorf("failed to close SQLite connection: %w", err)
	}

	if p.config.GetEnableLogging() {
		logger.Info("SQLite connection closed: name=%s", p.config.GetName())
	}

	p.db = nil
	return nil
}

// HealthCheck verifies the SQLite connection is alive
func (p *SQLiteProvider) HealthCheck(ctx context.Context) error {
	if p.db == nil {
		return fmt.Errorf("database connection is nil")
	}

	// Use a short timeout for health checks
	healthCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	// Execute a simple query to verify the database is accessible
	var result int
	run := func() error { return p.getDB().QueryRowContext(healthCtx, "SELECT 1").Scan(&result) }
	err := run()
	if isDBClosed(err) {
		if reconnErr := p.reconnectDB(); reconnErr == nil {
			err = run()
		}
	}
	if err != nil {
		return fmt.Errorf("health check failed: %w", err)
	}

	if result != 1 {
		return fmt.Errorf("health check returned unexpected result: %d", result)
	}

	return nil
}

// WithDBFactory configures a factory used to reopen the database connection if it is closed.
func (p *SQLiteProvider) WithDBFactory(factory func() (*sql.DB, error)) *SQLiteProvider {
	p.dbFactory = factory
	return p
}

func (p *SQLiteProvider) getDB() *sql.DB {
	p.dbMu.RLock()
	defer p.dbMu.RUnlock()
	return p.db
}

func (p *SQLiteProvider) reconnectDB() error {
	if p.dbFactory == nil {
		return fmt.Errorf("no db factory configured for reconnect")
	}
	newDB, err := p.dbFactory()
	if err != nil {
		return err
	}
	p.dbMu.Lock()
	p.db = newDB
	p.dbMu.Unlock()
	return nil
}

// GetNative returns the native *sql.DB connection
func (p *SQLiteProvider) GetNative() (*sql.DB, error) {
	if p.db == nil {
		return nil, fmt.Errorf("database connection is not initialized")
	}
	return p.db, nil
}

// GetMongo returns an error for SQLite (not a MongoDB connection)
func (p *SQLiteProvider) GetMongo() (*mongo.Client, error) {
	return nil, ErrNotMongoDB
}

// Stats returns connection pool statistics
func (p *SQLiteProvider) Stats() *ConnectionStats {
	if p.db == nil {
		return &ConnectionStats{
			Name:      p.config.GetName(),
			Type:      "sqlite",
			Connected: false,
		}
	}

	stats := p.db.Stats()

	return &ConnectionStats{
		Name:              p.config.GetName(),
		Type:              "sqlite",
		Connected:         true,
		OpenConnections:   stats.OpenConnections,
		InUse:             stats.InUse,
		Idle:              stats.Idle,
		WaitCount:         stats.WaitCount,
		WaitDuration:      stats.WaitDuration,
		MaxIdleClosed:     stats.MaxIdleClosed,
		MaxLifetimeClosed: stats.MaxLifetimeClosed,
	}
}
