package service

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"gorm.io/driver/postgres"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var (
	backendStore Store = unavailableStore{err: errors.New("backend store not initialized")}

	defaultStoreOnce sync.Once
	defaultStoreErr  error
)

func setBackendStore(store Store) {
	if store == nil {
		backendStore = unavailableStore{err: errors.New("backend store not initialized")}
		return
	}
	backendStore = store
}

func bootstrapDefaultStore() (Store, error) {
	defaultStoreOnce.Do(func() {
		db, err := openDBFromEnv()
		if err != nil {
			defaultStoreErr = err
			setBackendStore(unavailableStore{err: err})
			return
		}
		store, err := newGORMStore(db)
		if err != nil {
			defaultStoreErr = err
			setBackendStore(unavailableStore{err: err})
			return
		}
		setBackendStore(store)
	})
	return backendStore, defaultStoreErr
}

func openDBFromEnv() (*gorm.DB, error) {
	dsn := strings.TrimSpace(os.Getenv("DATABASE_URL"))
	if dsn == "" {
		return nil, errors.New("DATABASE_URL is required")
	}
	return openDB(dsn)
}

func openDB(dsn string) (*gorm.DB, error) {
	config := &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}
	var (
		db  *gorm.DB
		err error
	)
	if strings.HasPrefix(dsn, "sqlite:") || strings.HasPrefix(dsn, "file:") || dsn == ":memory:" {
		db, err = gorm.Open(sqlite.Open(dsn), config)
	} else {
		db, err = gorm.Open(postgres.Open(dsn), config)
	}
	if err != nil {
		return nil, fmt.Errorf("open database: %w", err)
	}
	sqlDB, err := db.DB()
	if err != nil {
		return nil, fmt.Errorf("database handle: %w", err)
	}
	sqlDB.SetConnMaxLifetime(5 * time.Minute)
	sqlDB.SetMaxIdleConns(5)
	sqlDB.SetMaxOpenConns(10)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := sqlDB.PingContext(ctx); err != nil {
		return nil, fmt.Errorf("ping database: %w", err)
	}
	return db, nil
}

type unavailableStore struct {
	err error
}

func (s unavailableStore) Ping(context.Context) error { return s.err }

func (s unavailableStore) UserByLogin(context.Context, string) (UserProfile, bool, error) {
	return UserProfile{}, false, s.err
}

func (s unavailableStore) UserByID(context.Context, string) (UserProfile, bool, error) {
	return UserProfile{}, false, s.err
}

func (s unavailableStore) AddUser(context.Context, string, string, string) (UserProfile, error) {
	return UserProfile{}, s.err
}

func (s unavailableStore) UpdatePassword(context.Context, string, string) error { return s.err }

func (s unavailableStore) ListUsers(context.Context) ([]UserProfile, error) { return nil, s.err }

func (s unavailableStore) ListContent(context.Context, ContentFilter) ([]ContentItem, error) {
	return nil, s.err
}

func (s unavailableStore) ContentByID(context.Context, string) (ContentItem, bool, error) {
	return ContentItem{}, false, s.err
}

func (s unavailableStore) AddContent(context.Context, ContentItem) (ContentItem, error) {
	return ContentItem{}, s.err
}

func (s unavailableStore) PatchContent(context.Context, string, ContentItem) (ContentItem, bool, error) {
	return ContentItem{}, false, s.err
}

func (s unavailableStore) DeleteContent(context.Context, string) (bool, error) { return false, s.err }

func (s unavailableStore) ListCategories(context.Context) ([]string, error) { return nil, s.err }

func (s unavailableStore) ListTags(context.Context) ([]string, error) { return nil, s.err }

func (s unavailableStore) ListSpeakers(context.Context) ([]Speaker, error) { return nil, s.err }

func (s unavailableStore) AddFile(context.Context, StoredFile) (StoredFile, error) {
	return StoredFile{}, s.err
}

func (s unavailableStore) FileByID(context.Context, string) (StoredFile, bool, error) {
	return StoredFile{}, false, s.err
}

func (s unavailableStore) UpsertSubscription(context.Context, string, string) ([]string, error) {
	return nil, s.err
}

func (s unavailableStore) ListNotifications(context.Context) ([]NotificationItem, error) {
	return nil, s.err
}

func (s unavailableStore) MarkNotificationRead(context.Context, string) (*NotificationItem, error) {
	return nil, s.err
}

func (s unavailableStore) ListComments(context.Context, string) ([]CommentItem, error) {
	return nil, s.err
}

func (s unavailableStore) AddComment(context.Context, string, UserProfile, string) (CommentItem, error) {
	return CommentItem{}, s.err
}

func (s unavailableStore) CountComments(context.Context) (int, error) { return 0, s.err }

func (s unavailableStore) ListAudit(context.Context) ([]AuditItem, error) { return nil, s.err }
