package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"

	"git.kirsle.net/apps/gophertype/pkg"
	"git.kirsle.net/apps/gophertype/pkg/console"
	_ "git.kirsle.net/apps/gophertype/pkg/controllers"
	"git.kirsle.net/apps/gophertype/pkg/models"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	jsondb "github.com/kirsle/blog/jsondb"
	mComments "github.com/kirsle/blog/models/comments"
	mPosts "github.com/kirsle/blog/models/posts"
)

// Command-line flags.
var (
	optSourceRoot string
	optRoot       string

	// Database option flags.
	optSQLite   string
	optPostgres string
	optMySQL    string

	// Chosen DB options.
	dbDriver string
	dbPath   string
)

// Other global variables
var (
	JsonDB *jsondb.DB
)

func init() {
	flag.StringVar(&optSourceRoot, "srcroot", "", "User root from old kirsle/blog website")
	flag.StringVar(&optRoot, "root", "", "User root for GopherType")

	// Database driver. Choose one.
	flag.StringVar(&optSQLite, "sqlite3", "", "Use SQLite database, default 'database.sqlite'")
	flag.StringVar(&optPostgres, "postgres", "",
		"Use Postgres database, format: "+
			"host=myhost port=myport user=gorm dbname=gorm password=mypassword")
	flag.StringVar(&optMySQL, "mysql", "",
		"Use MySQL database, format: "+
			"user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
}

func main() {
	console.SetDebug(false)
	flag.Parse()

	// Validate the choice of database.
	if optSQLite != "" {
		dbDriver = "sqlite3"
		dbPath = optSQLite
	} else if optPostgres != "" {
		dbDriver = "postgres"
		dbPath = optPostgres
	} else if optMySQL != "" {
		dbDriver = "mysql"
		dbPath = optMySQL
	} else {
		fmt.Print(
			"Specify a DB driver for Gophertype, similar to the gophertype command.",
		)
		os.Exit(1)
	}

	// Validate the roots are given.
	if optSourceRoot == "" {
		fmt.Print(
			"Missing -srcroot parameter: this should be the User Root from the legacy kirsle/blog site.",
		)
		os.Exit(1)
	}
	if optRoot == "" {
		fmt.Print(
			"Missing required -root parameter: this is the User Root for Gophertype",
		)
	}

	// Initialize the old JsonDB.

	app := gophertype.NewSite(optRoot)
	app.UseDB(dbDriver, dbPath)

	initJsonDB()
	doMigrate()
}

// Initialize the old kirsle/blog JsonDB.
func initJsonDB() {
	JsonDB = jsondb.New(filepath.Join(optSourceRoot, ".private"))
	mPosts.DB = JsonDB
	mComments.DB = JsonDB
}

// doMigrate is the head of the migrate functions.
func doMigrate() {
	migratePosts()
	migrateComments()
	resetSerial("posts")
}

// migratePosts migrates blog posts over.
func migratePosts() {
	console.Warn("BEGIN: Migrating blog posts")
	idx, err := mPosts.GetIndex()
	if err != nil {
		panic("migratePosts: GetIndex: " + err.Error())
	}

	// Sort the IDs to make it pretty.
	var sortedIds = []int{}
	for id := range idx.Posts {
		sortedIds = append(sortedIds, id)
	}
	sort.Ints(sortedIds)

	for _, id := range sortedIds {
		post, err := mPosts.Load(id)
		if err != nil {
			panic(fmt.Sprintf("migratePosts: error loading legacy post ID %d: %s", id, err))
		}

		console.Info("Post ID %d: %s", id, post.Title)

		// Create the post in Gophertype.
		p := models.Post{
			Title:          post.Title,
			Fragment:       post.Fragment,
			ContentType:    post.ContentType,
			AuthorID:       post.AuthorID,
			Body:           post.Body,
			Privacy:        post.Privacy,
			Sticky:         post.Sticky,
			EnableComments: post.EnableComments,
		}
		p.ID = post.ID
		p.CreatedAt = post.Created
		p.UpdatedAt = post.Updated

		// Convert tags.
		for _, tag := range post.Tags {
			p.Tags = append(p.Tags, models.TaggedPost{
				Tag: tag,
			})
		}

		err = p.Save()
		if err != nil {
			console.Error("Error saving post %d: %s", p.ID, err)
		}

		// Set the correct updated_at time.
		models.DB.Table("posts").Where("id = ?", p.ID).Updates(map[string]interface{}{
			"updated_at": post.Updated,
		})
	}

	console.Warn("FINISH: Migrating blog posts")
}

// migrateComments migrates comments over.
func migrateComments() {
	console.Warn("BEGIN: Migrating comments")

	// Only migrate comments one time.
	var count int
	models.DB.Model(&models.Comment{}).Count(&count)
	if count > 0 {
		console.Info("Comments already seem imported (count > 0, count == %d); skipping.", count)
		return
	}

	// Find all the comment threads.
	files, err := JsonDB.List("comments/threads")
	if err != nil {
		panic("Error listing comments: " + err.Error())
	}
	for _, file := range files {
		document := filepath.Base(file)
		thread, err := mComments.Load(document)
		if err != nil {
			panic(fmt.Sprintf("Error loading comment thread %s: %s", file, err))
		}

		for _, comment := range thread.Comments {
			// Migrate to the new model.
			com := models.Comment{
				Thread:      thread.ID,
				UserID:      comment.UserID,
				Name:        comment.Name,
				Email:       comment.Email,
				Avatar:      comment.Avatar,
				Body:        comment.Body,
				EditToken:   comment.EditToken,
				DeleteToken: comment.DeleteToken,
			}
			com.CreatedAt = comment.Created
			com.UpdatedAt = comment.Updated

			// Special case for guestbook page.
			if thread.ID == "guestbook" {
				com.OriginURL = "/guestbook"
			}

			if err := com.Save(); err != nil {
				console.Error("Error saving comment: %s", err)
			}
		}
	}

	console.Warn("FINISH: Migrating comments")
}

// resetSerial fixes the auto-incrementing ID for the next row added.
func resetSerial(table string) {
	// Get the max ID from the table.
	result := struct {
		Max int
	}{}
	models.DB.Table(table).Select("max(id) AS max").Scan(&result)

	// Run the query based on dialect.
	var query string
	switch dbDriver {
	case "postgres":
		query = fmt.Sprintf("ALTER SEQUENCE %s RESTART WITH %d", table+"_id_seq", result.Max+1)
	case "sqlite3":
		query = fmt.Sprintf("UPDATE SQLITE_SEQUENCE SET SEQ=%d WHERE NAME=%s", result.Max+1, table)
	case "mysql":
		query = fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT = %d", table, result.Max+1)
	}

	if len(query) > 0 {
		var noop []string
		r := models.DB.Raw(query).Scan(&noop)
		if r.Error != nil {
			console.Error("Error with query `%s`: %s", query, r.Error)
		}
	}
}
