package pgtables

import (
	"fmt"
	"log"
	"sort"

	"code.justin.tv/release/trace/pgparse"
)

var enableDebugLog bool = false

func debugLogf(format string, v ...interface{}) {
	if enableDebugLog {
		log.Printf(format, v...)
	}
}

type statementTracker struct {
	err error

	tables        map[string]string
	columns       map[string]map[string]struct{}
	substatements []*statementTracker
}

func newStatementTracker() *statementTracker {
	return &statementTracker{
		tables:        make(map[string]string),
		columns:       make(map[string]map[string]struct{}),
		substatements: nil,
	}
}

func (tr *statementTracker) Err() error {
	return tr.err
}

func (tr *statementTracker) Tables() []string {
	tables := make(map[string]struct{})
	for _, table := range tr.tables {
		tables[table] = struct{}{}
	}
	for _, sub := range tr.substatements {
		for _, table := range sub.Tables() {
			tables[table] = struct{}{}
		}
	}
	ordered := make([]string, 0, len(tables))
	for table := range tables {
		ordered = append(ordered, table)
	}
	sort.Strings(ordered)
	return ordered
}

func (tr *statementTracker) loadStmt(stmt pgparse.Statement) {
	if tr.err != nil {
		return
	}

	switch stmt := stmt.(type) {
	case *pgparse.Select:
		tr.loadSelect(stmt)
	case *pgparse.Insert:
		tr.loadInsert(stmt)
	case *pgparse.Update:
		tr.loadUpdate(stmt)
	case *pgparse.Delete:
		tr.loadDelete(stmt)

	default:
		tr.err = fmt.Errorf("loadStmt unknown type: %T", stmt)
	}
}

func (tr *statementTracker) loadSelect(stmt *pgparse.Select) {
	if tr.err != nil {
		return
	}

	for _, comment := range stmt.Comments {
		debugLogf("comment: %q", comment)
	}

	debugLogf("distinct: %q", stmt.Distinct)

	for _, expr := range stmt.SelectExprs {
		switch expr := expr.(type) {
		case *pgparse.NonStarExpr:
			debugLogf("expr.As: %q", expr.As)
			debugLogf("expr.Expr: %T", expr.Expr)
			switch expr := expr.Expr.(type) {
			case pgparse.NumVal:
				val, err := pgparse.AsInterface(expr)
				if err != nil {
					tr.err = fmt.Errorf("AsInterface: %v", err)
					return
				}
				debugLogf("val: %T", val)
				debugLogf("val: %s", val)
			}
		}
	}

	for _, table := range stmt.From {
		tr.loadTableExpr(table)
	}

	if stmt.Where != nil {
		debugLogf("where: %q", stmt.Where.Type)
		tr.loadExpr(stmt.Where.Expr)
	}

	for _, gb := range stmt.GroupBy {
		debugLogf("group by: %T", gb)
	}

	if stmt.Having != nil {
		debugLogf("having: %q", stmt.Having.Type)
	}
}

func (tr *statementTracker) loadInsert(stmt *pgparse.Insert) {
	if tr.err != nil {
		return
	}

	name := string(stmt.Table.Name)
	tr.tables[name] = name
}

func (tr *statementTracker) loadUpdate(stmt *pgparse.Update) {
	if tr.err != nil {
		return
	}

	name := string(stmt.Table.Name)
	tr.tables[name] = name
}

func (tr *statementTracker) loadDelete(stmt *pgparse.Delete) {
	if tr.err != nil {
		return
	}

	name := string(stmt.Table.Name)
	tr.tables[name] = name
}

func (tr *statementTracker) loadTableExpr(table pgparse.TableExpr) {
	if tr.err != nil {
		return
	}

	switch table := table.(type) {
	case *pgparse.AliasedTableExpr:
		switch expr := table.Expr.(type) {
		case *pgparse.TableName:
			name := string(expr.Name)
			as := string(table.As)
			if as == "" {
				as = name
			}
			tr.tables[as] = name
		case *pgparse.Subquery:
			tr.loadExpr(expr)

		default:
			tr.err = fmt.Errorf("loadTableExpr1 unknown type: %T", expr)
			return
		}
	case *pgparse.JoinTableExpr:
		tr.loadTableExpr(table.LeftExpr)
		tr.loadTableExpr(table.RightExpr)
		tr.loadExpr(table.On)
		// tr.err = fmt.Errorf("join not yet supported")
		// return

	default:
		tr.err = fmt.Errorf("loadTableExpr2 unknown type: %T", table)
		return
	}
}

func (tr *statementTracker) loadExpr(expr pgparse.Expr) {
	if tr.err != nil {
		return
	}

	switch expr := expr.(type) {
	case *pgparse.AndExpr:
		tr.loadExpr(expr.Left)
		tr.loadExpr(expr.Right)
	case *pgparse.OrExpr:
		tr.loadExpr(expr.Left)
		tr.loadExpr(expr.Right)
	case *pgparse.ComparisonExpr:
		tr.loadExpr(expr.Left)
		tr.loadExpr(expr.Right)
	case *pgparse.ParenBoolExpr:
		tr.loadExpr(expr.Expr)
	case *pgparse.NotExpr:
		tr.loadExpr(expr.Expr)
	case *pgparse.NullCheck:

	case *pgparse.FuncExpr:
		for _, expr := range expr.Exprs {
			tr.loadSelectExpr(expr)
		}
	case *pgparse.RangeCond:
		tr.loadExpr(expr.Left)
		tr.loadExpr(expr.From)
		tr.loadExpr(expr.To)
	case *pgparse.BinaryExpr:
		tr.loadExpr(expr.Left)
		tr.loadExpr(expr.Right)

	case *pgparse.Subquery:
		sub := newStatementTracker()
		sub.loadStmt(expr.Select)
		tr.substatements = append(tr.substatements, sub)

	case *pgparse.ColName:
		column := string(expr.Name)
		table := string(expr.Qualifier)
		if _, ok := tr.columns[table]; !ok {
			tr.columns[table] = make(map[string]struct{})
		}
		tr.columns[table][column] = struct{}{}

	case *pgparse.IndexedExpr:
		tr.loadExpr(expr.Expr)
		tr.loadExpr(expr.Index)

	case pgparse.ValTuple:
	case pgparse.StrVal:
	case pgparse.NumVal:
	case *pgparse.NullVal:
	case pgparse.ValArg:

	default:
		tr.err = fmt.Errorf("loadExpr unknown type: %T", expr)
		return
	}
}

func (tr *statementTracker) loadSelectExpr(expr pgparse.SelectExpr) {
	if tr.err != nil {
		return
	}

	switch expr := expr.(type) {
	case *pgparse.NonStarExpr:
		tr.loadExpr(expr.Expr)

	default:
		tr.err = fmt.Errorf("loadSelectExpr unknown type: %T", expr)
		return
	}
}
