package mysql

import (
	"fmt"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/tidb/ast"
	"github.com/pingcap/tidb/model"
	"github.com/pingcap/tidb/parser"
)

// TableName is the Table configuration
// slave restrict replication to a given table
type TableName struct {
	Schema string
	Name   string
}

type ddlNode struct {
	sql      string
	table    *TableName
	newTable *TableName
	stmt     ast.StmtNode
}

// resolveDDLSQL resolve to one ddl sql
// example: drop table test.a,test2.b -> drop table test.a; drop table test2.b;
// 1. add schema context like 'use db; create table (id int)'. implements AST to SQL in the future
// 2. modify ast.StmtNode for later use in mysql/ddl
func resolveDDLSQL(sql string, ctxSchema string) (ddls []*ddlNode, err error) {
	stmt, err := parser.New().ParseOneStmt(sql, "", "")
	if err != nil {
		return nil, errors.Trace(err)
	}

	_, isDDL := stmt.(ast.DDLNode)
	if !isDDL {
		// let ddls be empty
		return
	}

	switch v := stmt.(type) {
	case *ast.CreateDatabaseStmt:
		sql = createDatabaseStmtToSQL(v)
		ddls = append(ddls, &ddlNode{
			sql:  sql,
			stmt: v,
		})

	case *ast.DropDatabaseStmt:
		sql = dropDatabaseStmtToSQL(v)
		ddls = append(ddls, &ddlNode{
			sql:  sql,
			stmt: v,
		})

	// case *ast.AlterDatabaseStmt:
	// TODO: alter database statement(tidb not support currently)

	case *ast.CreateTableStmt:
		setSchemaIfNotExists(v.Table, ctxSchema)
		if v.ReferTable != nil {
			setSchemaIfNotExists(v.ReferTable, ctxSchema)
		}
		ddls = append(ddls, &ddlNode{
			sql:   fmt.Sprintf("USE `%s`; %s", v.Table.Schema.O, sql),
			table: toTableName(v.Table),
			stmt:  v,
		})

	case *ast.DropTableStmt:
		for _, t := range v.Tables {
			setSchemaIfNotExists(t, ctxSchema)
			sql = dropTableStmtToSQL(t, v.IfExists)
			ddls = append(ddls, &ddlNode{
				sql:   sql,
				table: toTableName(t),
				stmt:  &ast.DropTableStmt{IfExists: v.IfExists, Tables: []*ast.TableName{t}},
			})
		}

	case *ast.AlterTableStmt:
		setSchemaIfNotExists(v.Table, ctxSchema)
		tempSpecs := v.Specs
		newTable := &ast.TableName{}
		log.Warnf("[syncer] will split alter table statement: %v", sql)
		for i := range tempSpecs {
			v.Specs = tempSpecs[i : i+1]
			splitted := alterTableStmtToSQL(v.Table, newTable, v.Specs[0], ctxSchema)
			log.Warnf("[syncer] splitted alter table statement: %v", splitted)
			for _, sql := range splitted {
				ddls = append(ddls, &ddlNode{
					sql:      sql,
					table:    toTableName(v.Table),
					newTable: toTableName(newTable),
					stmt: &ast.AlterTableStmt{
						Table: v.Table,
						Specs: v.Specs,
					},
				})
			}
		}

	case *ast.RenameTableStmt:
		for _, t2t := range v.TableToTables {
			setSchemaIfNotExists(t2t.OldTable, ctxSchema)
			setSchemaIfNotExists(t2t.NewTable, ctxSchema)
			sql = renameTableStmtToSQL(t2t)
			ddls = append(ddls, &ddlNode{
				sql:      sql,
				table:    toTableName(t2t.OldTable),
				newTable: toTableName(t2t.NewTable),
				stmt: &ast.RenameTableStmt{
					OldTable: t2t.OldTable,
					NewTable: t2t.NewTable,
				},
			})
		}

	case *ast.TruncateTableStmt:
		setSchemaIfNotExists(v.Table, ctxSchema)
		ddls = append(ddls, &ddlNode{
			sql:   truncateTableStmtToSQL(v),
			table: toTableName(v.Table),
			stmt:  v,
		})

	case *ast.CreateIndexStmt:
		setSchemaIfNotExists(v.Table, ctxSchema)
		ddls = append(ddls, &ddlNode{
			// TODO: refactor it. remove `USE xxx`.
			sql:   fmt.Sprintf("USE `%s`; %s", v.Table.Schema.O, sql),
			table: toTableName(v.Table),
			stmt:  v,
		})

	case *ast.DropIndexStmt:
		setSchemaIfNotExists(v.Table, ctxSchema)
		ddls = append(ddls, &ddlNode{
			sql:   dropIndexStmtToSQL(v),
			table: toTableName(v.Table),
			stmt:  v,
		})

	default:
		// according to tidb source code, there are only 9 statements(the above statements) that implements DDLNode interface.
		panic("unreachable code")
	}
	return ddls, nil
}

func toTableName(table *ast.TableName) *TableName {
	return &TableName{
		Schema: table.Schema.O,
		Name:   table.Name.O,
	}
}

func setSchemaIfNotExists(table *ast.TableName, schemaName string) {
	if table.Schema.O == "" {
		table.Schema = model.NewCIStr(schemaName)
	}
}
