package initialize

import (
	"fmt"

	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/pkg/errors"
	"gorm.io/gorm"
)

type schemaTablePatch struct {
	table string
	ddl   string
}

type schemaColumnPatch struct {
	table  string
	column string
	ddl    string
}

type schemaColumnDefinitionPatch struct {
	table           string
	column          string
	dataType        string
	characterMaxLen *int64
	ddl             string
}

func EnsureSchemaCompatibility(ctx *svc.ServiceContext) error {
	tablePatches := []schemaTablePatch{
		{
			table: "log_message",
			ddl: `CREATE TABLE IF NOT EXISTS log_message (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  platform VARCHAR(32) NOT NULL,
  app_version VARCHAR(32) NULL,
  os_name VARCHAR(32) NULL,
  os_version VARCHAR(32) NULL,
  device_id VARCHAR(64) NULL,
  user_id BIGINT NULL DEFAULT NULL,
  session_id VARCHAR(64) NULL,
  level TINYINT UNSIGNED NOT NULL DEFAULT 3,
  error_code VARCHAR(64) NULL,
  message TEXT NOT NULL,
  stack MEDIUMTEXT NULL,
  context JSON NULL,
  client_ip VARCHAR(45) NULL,
  user_agent VARCHAR(255) NULL,
  locale VARCHAR(16) NULL,
  digest VARCHAR(64) NULL,
  occurred_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_digest (digest),
  KEY idx_platform_time (platform, created_at),
  KEY idx_user_time (user_id, created_at),
  KEY idx_device_time (device_id, created_at),
  KEY idx_error_code (error_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
		},
		{
			table: "user_family",
			ddl: `CREATE TABLE IF NOT EXISTS user_family (
  id BIGINT NOT NULL AUTO_INCREMENT,
  owner_user_id BIGINT NOT NULL COMMENT 'Owner User ID',
  max_members INT NOT NULL DEFAULT 5 COMMENT 'Max members in family',
  status TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1=active, 0=disabled',
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
  deleted_at DATETIME(3) DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_owner_user_id (owner_user_id),
  KEY idx_status (status),
  KEY idx_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
		},
		{
			table: "user_family_member",
			ddl: `CREATE TABLE IF NOT EXISTS user_family_member (
  id BIGINT NOT NULL AUTO_INCREMENT,
  family_id BIGINT NOT NULL COMMENT 'Family ID',
  user_id BIGINT NOT NULL COMMENT 'Member User ID',
  role TINYINT NOT NULL DEFAULT 2 COMMENT 'Role: 1=owner, 2=member',
  status TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1=active, 2=left, 3=removed',
  join_source VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'Join source',
  joined_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  left_at DATETIME(3) DEFAULT NULL,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
  deleted_at DATETIME(3) DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_user_id (user_id),
  KEY idx_family_status (family_id, status),
  KEY idx_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
		},
	}

	columnPatches := []schemaColumnPatch{
		{
			table:  "user",
			column: "rules",
			ddl:    "ALTER TABLE `user` ADD COLUMN `rules` TEXT NULL COMMENT 'User rules for subscription';",
		},
		{
			table:  "user",
			column: "last_login_time",
			ddl:    "ALTER TABLE `user` ADD COLUMN `last_login_time` DATETIME DEFAULT NULL COMMENT 'Last Login Time';",
		},
		{
			table:  "user",
			column: "member_status",
			ddl:    "ALTER TABLE `user` ADD COLUMN `member_status` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'Member Status';",
		},
		{
			table:  "user",
			column: "remark",
			ddl:    "ALTER TABLE `user` ADD COLUMN `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Remark';",
		},
		{
			table:  "user_subscribe",
			column: "note",
			ddl:    "ALTER TABLE `user_subscribe` ADD COLUMN `note` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'User note for subscription';",
		},
		{
			table:  "redemption_code",
			column: "status",
			ddl:    "ALTER TABLE `redemption_code` ADD COLUMN `status` TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1=enabled, 0=disabled';",
		},
		{
			table:  "user_auth_methods",
			column: "deleted_at",
			ddl:    "ALTER TABLE `user_auth_methods` ADD COLUMN `deleted_at` DATETIME(3) DEFAULT NULL COMMENT 'Deletion Time';",
		},
	}

	// Index patches: ensure critical indexes exist
	type schemaIndexPatch struct {
		table string
		index string
		ddl   string
	}
	indexPatches := []schemaIndexPatch{
		{
			table: "user_auth_methods",
			index: "idx_user_deleted_at",
			ddl:   "CREATE INDEX `idx_user_deleted_at` ON `user_auth_methods` (`user_id`, `deleted_at`);",
		},
	}

	varchar255 := int64(255)
	columnDefinitionPatches := []schemaColumnDefinitionPatch{
		{
			table:           "user_device",
			column:          "user_agent",
			dataType:        "varchar",
			characterMaxLen: &varchar255,
			ddl:             "ALTER TABLE `user_device` MODIFY COLUMN `user_agent` VARCHAR(255) NULL COMMENT 'Device User Agent.';",
		},
	}

	for _, patch := range tablePatches {
		exists, err := tableExists(ctx.DB, patch.table)
		if err != nil {
			return errors.Wrapf(err, "check table %s failed", patch.table)
		}
		if exists {
			continue
		}
		if err = ctx.DB.Exec(patch.ddl).Error; err != nil {
			return errors.Wrapf(err, "create table %s failed", patch.table)
		}
		logger.Infof("[SchemaCompat] created missing table: %s", patch.table)
	}

	for _, patch := range columnPatches {
		tblExists, err := tableExists(ctx.DB, patch.table)
		if err != nil {
			return errors.Wrapf(err, "check table %s failed", patch.table)
		}
		if !tblExists {
			logger.Infof("[SchemaCompat] skipping column patch %s.%s: table does not exist", patch.table, patch.column)
			continue
		}
		exists, err := columnExists(ctx.DB, patch.table, patch.column)
		if err != nil {
			return errors.Wrapf(err, "check column %s.%s failed", patch.table, patch.column)
		}
		if exists {
			continue
		}
		if err = ctx.DB.Exec(patch.ddl).Error; err != nil {
			return errors.Wrapf(err, "add column %s.%s failed", patch.table, patch.column)
		}
		logger.Infof("[SchemaCompat] added missing column: %s.%s", patch.table, patch.column)
	}

	for _, patch := range indexPatches {
		tblExists, err := tableExists(ctx.DB, patch.table)
		if err != nil {
			return errors.Wrapf(err, "check table %s failed", patch.table)
		}
		if !tblExists {
			continue
		}
		exists, err := indexExists(ctx.DB, patch.table, patch.index)
		if err != nil {
			return errors.Wrapf(err, "check index %s.%s failed", patch.table, patch.index)
		}
		if exists {
			continue
		}
		if err = ctx.DB.Exec(patch.ddl).Error; err != nil {
			return errors.Wrapf(err, "create index %s.%s failed", patch.table, patch.index)
		}
		logger.Infof("[SchemaCompat] created missing index: %s.%s", patch.table, patch.index)
	}

	for _, patch := range columnDefinitionPatches {
		tblExists, err := tableExists(ctx.DB, patch.table)
		if err != nil {
			return errors.Wrapf(err, "check table %s failed", patch.table)
		}
		if !tblExists {
			continue
		}
		matches, err := columnDefinitionMatches(ctx.DB, patch.table, patch.column, patch.dataType, patch.characterMaxLen)
		if err != nil {
			return errors.Wrapf(err, "check column definition %s.%s failed", patch.table, patch.column)
		}
		if matches {
			continue
		}
		if err = ctx.DB.Exec(patch.ddl).Error; err != nil {
			return errors.Wrapf(err, "modify column %s.%s failed", patch.table, patch.column)
		}
		logger.Infof("[SchemaCompat] repaired column definition: %s.%s", patch.table, patch.column)
	}

	return nil
}

func tableExists(db *gorm.DB, table string) (bool, error) {
	var count int64
	err := db.Raw("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?", table).Scan(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func columnExists(db *gorm.DB, table, column string) (bool, error) {
	var count int64
	err := db.Raw(
		"SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
		table,
		column,
	).Scan(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func indexExists(db *gorm.DB, table, index string) (bool, error) {
	var count int64
	err := db.Raw(
		"SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?",
		table,
		index,
	).Scan(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func columnDefinitionMatches(db *gorm.DB, table, column, dataType string, characterMaxLen *int64) (bool, error) {
	type columnMeta struct {
		DataType            string
		CharacterMaximumLen *int64
	}

	var meta columnMeta
	err := db.Raw(
		`SELECT DATA_TYPE AS data_type, CHARACTER_MAXIMUM_LENGTH AS character_maximum_len
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?`,
		table,
		column,
	).Scan(&meta).Error
	if err != nil {
		return false, err
	}
	if meta.DataType == "" {
		return false, nil
	}
	if meta.DataType != dataType {
		return false, nil
	}
	if characterMaxLen == nil {
		return true, nil
	}
	if meta.CharacterMaximumLen == nil {
		return false, nil
	}
	return *meta.CharacterMaximumLen == *characterMaxLen, nil
}

func _schemaCompatDebug(table, column string) string {
	if column == "" {
		return table
	}
	return fmt.Sprintf("%s.%s", table, column)
}
