package persistence

import (
	_ "embed"
	"fmt"
	"os"
	"path/filepath"

	sql "github.com/jmoiron/sqlx"
	"github.com/jmoiron/sqlx/reflectx"
	_ "github.com/mattn/go-sqlite3"

	"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
)

//go:embed schema.sql
var sql_init string

//go:embed default_profile.png
var default_profile_image []byte

type Profile struct {
	ProfileDir string
	DB         *sql.DB
}

var ErrTargetAlreadyExists = fmt.Errorf("Target already exists")

// Create a new profile in the given location.
// Fails if target location already exists (i.e., is a file or directory).
//
// args:
// - target_dir: location to create the new profile directory
//
// returns:
// - the newly created Profile
func NewProfile(target_dir string) (Profile, error) {
	if file_exists(target_dir) {
		return Profile{}, fmt.Errorf("Could not create target %q:\n  %w", target_dir, ErrTargetAlreadyExists)
	}

	sqlite_file := filepath.Join(target_dir, "twitter.db")
	profile_images_dir := filepath.Join(target_dir, "profile_images")
	default_profile_image_file := filepath.Join(target_dir, "profile_images/default_profile.png")
	link_thumbnails_dir := filepath.Join(target_dir, "link_preview_images")
	images_dir := filepath.Join(target_dir, "images")
	videos_dir := filepath.Join(target_dir, "videos")
	video_thumbnails_dir := filepath.Join(target_dir, "video_thumbnails")

	// Create the directory
	fmt.Printf("Creating new profile: %s\n", target_dir)
	err := os.Mkdir(target_dir, os.FileMode(0755))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating directory %q:\n  %w", target_dir, err)
	}

	// Create `twitter.db`
	fmt.Printf("Creating............. %s\n", sqlite_file)
	db := sql.MustOpen("sqlite3", sqlite_file+"?_foreign_keys=on")
	db.MustExec(sql_init)
	InitializeDatabaseVersion(db)
	db.Mapper = reflectx.NewMapperFunc("db", ToSnakeCase)

	// Create `profile_images`
	fmt.Printf("Creating............. %s/\n", profile_images_dir)
	err = os.Mkdir(profile_images_dir, os.FileMode(0755))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating %q:\n  %w", profile_images_dir, err)
	}
	// Put the default profile image in it
	fmt.Printf("Creating............. %s/\n", default_profile_image_file)
	err = os.WriteFile(default_profile_image_file, default_profile_image, os.FileMode(0644))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating default profile image file %q:\n  %w", default_profile_image, err)
	}

	// Create `link_thumbnail_images`
	fmt.Printf("Creating............. %s/\n", link_thumbnails_dir)
	err = os.Mkdir(link_thumbnails_dir, os.FileMode(0755))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating %q:\n  %w", link_thumbnails_dir, err)
	}

	// Create `images`
	fmt.Printf("Creating............. %s/\n", images_dir)
	err = os.Mkdir(images_dir, os.FileMode(0755))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating %q:\n  %w", images_dir, err)
	}

	// Create `videos`
	fmt.Printf("Creating............. %s/\n", videos_dir)
	err = os.Mkdir(videos_dir, os.FileMode(0755))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating %q:\n  %w", videos_dir, err)
	}

	// Create `video_thumbnails`
	fmt.Printf("Creating............. %s/\n", video_thumbnails_dir)
	err = os.Mkdir(video_thumbnails_dir, os.FileMode(0755))
	if err != nil {
		return Profile{}, fmt.Errorf("Error creating %q:\n  %w", video_thumbnails_dir, err)
	}

	return Profile{ProfileDir: target_dir, DB: db}, nil
}

// Loads the profile at the given location.  Fails if the given directory is not a Profile.
//
// args:
// - profile_dir: location to check for the profile
//
// returns:
// - the loaded Profile
func LoadProfile(profile_dir string) (Profile, error) {
	sqlite_file := filepath.Join(profile_dir, "twitter.db")
	if !file_exists(sqlite_file) {
		return Profile{}, fmt.Errorf("Invalid profile, could not find file: %s", sqlite_file)
	}

	db := sql.MustOpen("sqlite3", fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL", sqlite_file))
	db.Mapper = reflectx.NewMapperFunc("db", ToSnakeCase)

	ret := Profile{
		ProfileDir: profile_dir,
		DB:         db,
	}
	err := ret.check_and_update_version()
	return ret, err
}

func (p Profile) ListSessions() []scraper.UserHandle {
	result, err := filepath.Glob(filepath.Join(p.ProfileDir, "*.session"))
	if err != nil {
		panic(err)
	}
	ret := []scraper.UserHandle{}
	for _, filename := range result {
		ret = append(ret, scraper.UserHandle(filepath.Base(filename[:len(filename)-len(".session")])))
	}
	return ret
}
