package service

import (
	"crawler/config"
	"strings"
	"time"

	"database/sql"
	"fmt"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var (
	DB_MOVIEW  DB
	DB_CRAWLER DB
	DB_PLAYR   DB
)

type DB struct {
	SQLDB  *sql.DB
	GORMDB *gorm.DB
	DSN    string
	Err    error
}

// 2025.08.08 비동기 처리를 위한 수정
type logItem struct {
	Action  int
	Query   string
	Comment string
}

var (
	logChannel     = make(chan logItem, 10000)
	flushN         = 200                    // 200개 쌓이면 flush
	flushT         = 250 * time.Millisecond // 250ms마다 flush
	stopChannel    = make(chan struct{})
	stoppedChannel = make(chan struct{})
)

func InitLogWorker(db *sql.DB) {
	go func() {
		defer close(stoppedChannel)
		buf := make([]logItem, 0, flushN)
		ticker := time.NewTicker(flushT)
		defer ticker.Stop()

		for {
			select {
			case item := <-logChannel:
				buf = append(buf, item)
				if len(buf) >= flushN {
					flushLogs(db, buf)
					buf = buf[:0]
				}
			case <-ticker.C:
				if len(buf) > 0 {
					flushLogs(db, buf)
					buf = buf[:0]
				}
			case <-stopChannel:
				for {
					select {
					case it := <-logChannel:
						buf = append(buf, it)
					default:
						if len(buf) > 0 {
							flushLogs(db, buf)
						}
						return
					}
				}
			}
		}
	}()
}

func StopLogWorker() {
	close(stopChannel) // stopChannel를 닫아 더 이상 logItem을 받지 않도록 함
	<-stoppedChannel   // flush 끝날 때까지 대기
}

// 다중 입력 SQL 생성
func flushLogs(db *sql.DB, items []logItem) {
	var sb strings.Builder
	sb.WriteString(`INSERT INTO tb_general_log (action, query, comment, created_at) VALUES `)

	args := make([]any, 0, len(items)*3)

	for i, it := range items {
		if i > 0 {
			sb.WriteByte(',')
		}
		sb.WriteString("(?, ?, ?, NOW())")
		args = append(args, it.Action, it.Query, it.Comment)
	}

	if _, err := db.Exec(sb.String(), args...); err != nil {
		fmt.Println("log flush error:", err)
	}
}

func SetConfig() config.DBAccount {
	var (
		env     = config.Env
		db      = config.DB
		account = config.DBAccount{}
	)

	// DB 환경설정
	switch env.DeveloperEnv {
	case config.LOCAL:
		account = db.Local
	case config.DEV:
		account = db.Dev
	default:
		fmt.Println("DB 설정이 잘못되었습니다.")
	}

	//fmt.Printf("\n%#v\n", account)

	return account
}

// DB 연결
func SetDatabase(dbName string) DB {
	var (
		env     = config.Env
		db      = config.DB
		account = SetConfig()
		err     error
	)

	if dbName != "" {
		account.Name = dbName
	}

	/*
	 * sqlDB
	 */
	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", account.User, account.Password, account.Address, account.Name)
	fmt.Printf("\nEnv : %s\n", env.DeveloperEnv)
	fmt.Printf("%s\n", dsn)
	SQLConnd, err := sql.Open(account.Driver, dsn)

	// sql.Open 은 실제 접속을 하지 않으므로 Ping 으로 확인한다.
	// Ping 에러를 err 에 담아, 아래에서 err 이 nil 이 아님을 보장한다. (기존: err.Error() nil 역참조 패닉)
	if err == nil {
		err = SQLConnd.Ping()
	}

	if err != nil {
		fmt.Printf("driver : %s\n", account.Driver)
		fmt.Printf("dsn : %s\n", dsn)
		fmt.Printf("sqlDB에 연결에 실패하였습니다.\n %v\n", err)
		if SQLConnd != nil {
			SQLConnd.Close()
		}
		return DB{}
	}

	SQLConnd.SetConnMaxLifetime(time.Duration(db.MaxLifetime) * time.Second)
	SQLConnd.SetConnMaxIdleTime(time.Duration(db.MaxIdleTime) * time.Second)
	SQLConnd.SetMaxIdleConns(db.MaxIdleConn)
	SQLConnd.SetMaxOpenConns(db.MaxOpenConn)

	/*
	 * GormDB
	 */
	GORMConnd, err := gorm.Open(mysql.New(mysql.Config{
		Conn: SQLConnd,
	}), &gorm.Config{})
	if err != nil {
		fmt.Printf("GormDB에 연결에 실패하였습니다.\n %v", err.Error())
		fmt.Println(err)
	}

	new := new(DB)
	new.SQLDB = SQLConnd
	new.GORMDB = GORMConnd
	new.DSN = dsn
	new.Err = err

	fmt.Println("Database was opened successfully !!")

	return *new
}

// DB 연결
func (db *DB) Connection(dbName string) DB {
	return SetDatabase(dbName)
}

// gorm 커넥션 연결
func (db *DB) GConnection(dbName string) DB {
	return SetDatabase(dbName)
}

// DB 오류 입력
func (db *DB) SetErrorLog(err error, query string) {
	var (
		conn         = DB_CRAWLER.SQLDB
		sql          = `CALL SP_ERROR_LOG(?, ?, ?);`
		errorMessage = strings.TrimSpace(strings.Split(err.Error(), ":")[1])
		errorNo      = strings.TrimSpace(strings.Replace(strings.Split(err.Error(), ":")[0], "Error ", "", 1))
	)

	conn.Exec(sql, errorNo, errorMessage, query)
	switch errorMessage {
	case "sql: no rows in result set":
	case "":
	default:
		fmt.Printf("DB error msg : %s\n", err.Error())
	}

	fmt.Printf("Exe error query : %s\n", query)
}

/*
 * DB SQL 입력
 * 2025.08.08 비동기 처리를 위한 수정으로 미사용
 */
/*
func (db *DB) SetGeneralLog(action int, query, comment string) {
	var (
		conn = DB_CRAWLER.SQLDB
		sql  = `
			INSERT INTO tb_general_log
			SET
				action = ?,
				query = ?,
				comment = ?,
				created_at = NOW();
		`
	)

	_, err := conn.Exec(sql, action, query, comment)
	if err != nil {
		db.SetErrorLog(err, sql)
	}
}
*/

func (db *DB) SetGeneralLog(action int, query, comment string) {
	if len(query) > 4000 {
		query = query[:4000] + "…(truncated)" // 너무 긴 쿼리는 잘라서 저장 (행 크기/페이지 split 완화)
	}

	select {
	case logChannel <- logItem{action, query, comment}:
	default:
		// 큐가 가득하면 발생
	}
}

// DB 연결 종료
func (db *DB) Close() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("Database recovered message: %s\n", err)
		}
	}()

	if DB_MOVIEW != (DB{}) {
		defer DB_MOVIEW.SQLDB.Close()
		DB_MOVIEW.SQLDB = nil
		DB_MOVIEW.GORMDB = nil
		DB_MOVIEW.DSN = ""
		DB_MOVIEW.Err = nil
	}

	if DB_CRAWLER != (DB{}) {
		defer DB_CRAWLER.SQLDB.Close()
		DB_CRAWLER.SQLDB = nil
		DB_CRAWLER.GORMDB = nil
		DB_CRAWLER.DSN = ""
		DB_CRAWLER.Err = nil
	}

	if DB_PLAYR != (DB{}) {
		defer DB_PLAYR.SQLDB.Close()
		DB_PLAYR.SQLDB = nil
		DB_PLAYR.GORMDB = nil
		DB_PLAYR.DSN = ""
		DB_PLAYR.Err = nil
	}

	fmt.Println("DB closed")
}
