package internal

import (
	"context"
	"fmt"
	"sync"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"mongo.games.com/goserver/core/logger"
)

type Config struct {
	Global    map[string]*DatabaseConfig
	Platforms map[string]map[string]*DatabaseConfig
}

type DatabaseConfig struct {
	HostName string // 主机地址
	HostPort int32  // 端口
	Database string // 数据库名
	Username string // 用户名
	Password string // 密码
	Options  string // 配置
}

type Collection struct {
	Database *Database
	*mongo.Collection
}

type Database struct {
	*DatabaseConfig
	Client     *mongo.Client
	Database   *mongo.Database
	Collection sync.Map
}

func (d *Database) Connect() error {
	if d.DatabaseConfig == nil {
		err := fmt.Errorf("mongo Connect error, DatabaseConifg not found")
		logger.Logger.Error(err)
		return err
	}

	login := ""
	if d.DatabaseConfig.Username != "" {
		login = d.DatabaseConfig.Username + ":" + d.DatabaseConfig.Password + "@"
	}
	host := d.DatabaseConfig.HostName
	if d.DatabaseConfig.HostName == "" {
		host = "127.0.0.1"
	}
	port := d.DatabaseConfig.HostPort
	if d.DatabaseConfig.HostPort == 0 {
		port = 27017
	}
	myOptions := d.DatabaseConfig.Options
	if myOptions != "" {
		myOptions = "?" + myOptions
	}

	s := fmt.Sprintf("mongodb://%s%s:%d/admin%s", login, host, port, myOptions)
	client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(s))
	if err != nil {
		logger.Logger.Errorf("mongo Connect %v error: %v config:%+v", s, err, *d.DatabaseConfig)
		return err
	}
	logger.Logger.Tracef("mongo connect success %+v", *d.DatabaseConfig)
	d.Client = client
	d.Database = client.Database(d.DatabaseConfig.Database)
	return nil
}

func (d *Database) GetCollection(name string) (*Collection, error) {
	if d.Database == nil {
		err := fmt.Errorf("mongo GetCollection error, collection:%v, database is nil", name)
		logger.Logger.Error(err)
		return nil, err
	}
	v, ok := d.Collection.Load(name)
	if !ok {
		v = &Collection{
			Database:   d,
			Collection: d.Database.Collection(name),
		}
		d.Collection.Store(name, v)
	}
	c, _ := v.(*Collection)
	return c, nil
}

type Manager struct {
	conf      *Config
	global    *sync.Map // 内部库名称:Database
	platforms *sync.Map // 平台id:内部库名称:Database
}

func (m *Manager) GetCollection(key, database, collection string) (*Collection, error) {
	d, err := m.GetDatabase(key, database)
	if err != nil {
		return nil, err
	}
	return d.GetCollection(collection)
}

func (m *Manager) GetDatabase(key, database string) (*Database, error) {
	switch key {
	case "global":
		v, ok := m.global.Load(database)
		if !ok {
			db := &Database{
				DatabaseConfig: m.conf.Global[database],
				Collection:     sync.Map{},
			}
			if err := db.Connect(); err != nil {
				return nil, err
			}
			v = db
			m.global.Store(database, v)
		}
		d, _ := v.(*Database)
		return d, nil

	default:
		var mp *sync.Map
		v, ok := m.platforms.Load(key) // 平台id
		if !ok {
			mp = new(sync.Map)
			m.platforms.Store(key, mp)
		} else {
			mp = v.(*sync.Map)
		}
		v, ok = mp.Load(database)
		if !ok {
			db := &Database{
				DatabaseConfig: m.conf.Platforms[key][database],
				Collection:     sync.Map{},
			}
			if err := db.Connect(); err != nil {
				return nil, err
			}
			v = db
			mp.Store(database, v)
		}
		d, _ := v.(*Database)
		return d, nil
	}
}

func (m *Manager) Restart(conf *Config) {
	logger.Logger.Infof("mongo manager restart...")
	old := *m
	time.AfterFunc(time.Minute, func() {
		Close(&old)
	})

	m.conf = conf
	m.global = &sync.Map{}
	m.platforms = &sync.Map{}
}

func Close(m *Manager) {
	logger.Logger.Infof("mongo manager close")
	m.global.Range(func(key, value any) bool {
		if v, ok := value.(*Database); ok {
			v.Client.Disconnect(nil)
		}
		return true
	})

	m.platforms.Range(func(key, value any) bool {
		if v, ok := value.(*sync.Map); ok {
			v.Range(func(key, value any) bool {
				if v, ok := value.(*Database); ok {
					v.Client.Disconnect(nil)
				}
				return true
			})
		}
		return true
	})
}

func (m *Manager) GetConfig() *Config {
	return m.conf
}

func NewManager(conf *Config) *Manager {
	return &Manager{
		conf:      conf,
		global:    &sync.Map{},
		platforms: &sync.Map{},
	}
}
