package service

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strings"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"oci-portal/internal/model"
)

type tenantDeleteResult struct {
	config         model.OciConfig
	deletedTaskIDs []uint
	channelsGone   bool
}

type tenantTaskAction struct {
	task       model.Task
	deleteTask bool
	payload    string
}

type tenancyCacheInvalidator interface {
	InvalidateTenancy(tenancyOCID string)
}

func (s *OciConfigService) deleteTenant(ctx context.Context, id uint) (*tenantDeleteResult, error) {
	result := &tenantDeleteResult{}
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return s.deleteTenantInTx(tx, id, result)
	})
	if err != nil {
		return nil, fmt.Errorf("delete oci config %d: %w", id, err)
	}
	return result, nil
}

func (s *OciConfigService) deleteTenantInTx(tx *gorm.DB, id uint, result *tenantDeleteResult) error {
	if err := lockTenant(tx, id, &result.config); err != nil {
		return err
	}
	if err := s.deleteTenantTasks(tx, id, result); err != nil {
		return err
	}
	if err := deleteTenantEvents(tx, id); err != nil {
		return err
	}
	if err := deleteTenantAI(tx, id, result); err != nil {
		return err
	}
	if err := deleteTenantSnapshots(tx, id); err != nil {
		return err
	}
	return deleteTenantConfig(tx, id)
}

func lockTenant(tx *gorm.DB, id uint, cfg *model.OciConfig) error {
	err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(cfg, id).Error
	if err != nil {
		return fmt.Errorf("load tenant: %w", err)
	}
	return nil
}

func (s *OciConfigService) deleteTenantTasks(tx *gorm.DB, id uint, result *tenantDeleteResult) error {
	var tasks []model.Task
	err := tx.Where("type IN ?", []string{
		model.TaskTypeSnatch, model.TaskTypeHealthCheck, model.TaskTypeCost,
	}).Find(&tasks).Error
	if err != nil {
		return fmt.Errorf("load tenant tasks: %w", err)
	}
	for i := range tasks {
		action, ok, err := planTenantTask(tasks[i], id)
		if err != nil {
			return err
		}
		if !ok {
			continue
		}
		if err := applyTenantTask(tx, action, result); err != nil {
			return err
		}
	}
	return nil
}

func planTenantTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
	switch task.Type {
	case model.TaskTypeSnatch:
		return planSnatchTask(task, id)
	case model.TaskTypeHealthCheck, model.TaskTypeCost:
		return planMultiTenantTask(task, id)
	default:
		return tenantTaskAction{}, false, nil
	}
}

func planSnatchTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
	var payload snatchPayload
	if err := json.Unmarshal([]byte(task.Payload), &payload); err != nil {
		logSkippedTask(task, err)
		return tenantTaskAction{}, false, nil
	}
	if payload.OciConfigID != id {
		return tenantTaskAction{}, false, nil
	}
	return tenantTaskAction{task: task, deleteTask: true}, true, nil
}

// logSkippedTask 记录 payload 无法解析而被跳过的任务:坏数据无法归属租户,
// fail-closed 会永久阻断删除且只能手工修库;改为保留原任务(不删不改)并放行删除。
func logSkippedTask(task model.Task, err error) {
	log.Printf("tenant delete: 任务 %d(%s)payload 无法解析,跳过处理: %v", task.ID, task.Type, err)
}

func planMultiTenantTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
	payload, err := decodeConfigIDs(task)
	if err != nil {
		logSkippedTask(task, err)
		return tenantTaskAction{}, false, nil
	}
	if len(payload.OciConfigIDs) == 0 {
		return tenantTaskAction{task: task, payload: task.Payload}, true, nil
	}
	remaining, found := removeConfigID(payload.OciConfigIDs, id)
	if !found {
		return tenantTaskAction{}, false, nil
	}
	if len(remaining) == 0 {
		return tenantTaskAction{task: task, deleteTask: true}, true, nil
	}
	payload.OciConfigIDs = remaining
	raw, err := json.Marshal(payload)
	if err != nil {
		return tenantTaskAction{}, false, fmt.Errorf("encode task %d payload: %w", task.ID, err)
	}
	return tenantTaskAction{task: task, payload: string(raw)}, true, nil
}

func decodeConfigIDs(task model.Task) (healthCheckPayload, error) {
	var payload healthCheckPayload
	if strings.TrimSpace(task.Payload) == "" {
		return payload, nil
	}
	if err := json.Unmarshal([]byte(task.Payload), &payload); err != nil {
		return payload, fmt.Errorf("parse task %d payload: %w", task.ID, err)
	}
	return payload, nil
}

func removeConfigID(ids []uint, id uint) ([]uint, bool) {
	remaining := make([]uint, 0, len(ids))
	found := false
	for _, candidate := range ids {
		if candidate == id {
			found = true
			continue
		}
		remaining = append(remaining, candidate)
	}
	return remaining, found
}

func applyTenantTask(tx *gorm.DB, action tenantTaskAction, result *tenantDeleteResult) error {
	if err := deleteTaskHistory(tx, action.task.ID); err != nil {
		return err
	}
	if action.deleteTask {
		if err := tx.Delete(&model.Task{}, action.task.ID).Error; err != nil {
			return fmt.Errorf("delete task %d: %w", action.task.ID, err)
		}
		result.deletedTaskIDs = append(result.deletedTaskIDs, action.task.ID)
		return nil
	}
	return resetTenantTask(tx, action)
}

func deleteTaskHistory(tx *gorm.DB, taskID uint) error {
	if err := tx.Where("task_id = ?", taskID).Delete(&model.TaskLog{}).Error; err != nil {
		return fmt.Errorf("delete task %d logs: %w", taskID, err)
	}
	key := deadAliasKey(taskID)
	if err := tx.Where("key = ?", key).Delete(&model.Setting{}).Error; err != nil {
		return fmt.Errorf("delete task %d state: %w", taskID, err)
	}
	return nil
}

func resetTenantTask(tx *gorm.DB, action tenantTaskAction) error {
	updates := map[string]any{
		"payload": action.payload, "last_run_at": nil,
		"last_error": "", "run_count": 0,
	}
	err := tx.Model(&model.Task{}).Where("id = ?", action.task.ID).Updates(updates).Error
	if err != nil {
		return fmt.Errorf("reset task %d: %w", action.task.ID, err)
	}
	return nil
}

func deleteTenantEvents(tx *gorm.DB, id uint) error {
	if err := lockTenantEventRows(tx, id); err != nil {
		return fmt.Errorf("load tenant log events: %w", err)
	}
	if err := deleteWhere(tx, &model.LogEvent{}, "oci_config_id = ?", id); err != nil {
		return fmt.Errorf("delete tenant log events: %w", err)
	}
	return nil
}

// lockTenantEventRows 对租户全部日志事件行加 FOR UPDATE 锁(SQLite 忽略,
// MySQL/PG 阻塞并发写入);事件可达数万,ID 不回传拼接 SQL,删除用条件语句,
// 避免绑定变量上限。
func lockTenantEventRows(tx *gorm.DB, id uint) error {
	var rows []model.LogEvent
	return tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id").
		Where("oci_config_id = ?", id).Order("id").Find(&rows).Error
}

func deleteTenantAI(tx *gorm.DB, id uint, result *tenantDeleteResult) error {
	channelIDs, err := lockedAiChannelIDs(tx, id)
	if err != nil {
		return fmt.Errorf("load tenant AI channels: %w", err)
	}
	if len(channelIDs) == 0 {
		return nil
	}
	callIDs, err := lockedAiCallIDs(tx, channelIDs)
	if err != nil {
		return fmt.Errorf("load tenant AI calls: %w", err)
	}
	if err := deleteTenantAIRows(tx, channelIDs, callIDs); err != nil {
		return err
	}
	result.channelsGone = true
	return reconcileAiProbeRows(tx, result)
}

func lockedAiChannelIDs(tx *gorm.DB, id uint) ([]uint, error) {
	var rows []model.AiChannel
	err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id").
		Where("oci_config_id = ?", id).Order("id").Find(&rows).Error
	ids := make([]uint, 0, len(rows))
	for _, row := range rows {
		ids = append(ids, row.ID)
	}
	return ids, err
}

func lockedAiCallIDs(tx *gorm.DB, channelIDs []uint) ([]uint, error) {
	var rows []model.AiCallLog
	err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id").
		Where("channel_id IN ?", channelIDs).Order("id").Find(&rows).Error
	ids := make([]uint, 0, len(rows))
	for _, row := range rows {
		ids = append(ids, row.ID)
	}
	return ids, err
}

func deleteTenantAIRows(tx *gorm.DB, channelIDs, callIDs []uint) error {
	if len(callIDs) > 0 {
		if err := deleteWhere(tx, &model.AiContentLog{}, "call_log_id IN ?", callIDs); err != nil {
			return fmt.Errorf("delete tenant AI content logs: %w", err)
		}
	}
	steps := []struct {
		name  string
		value any
	}{
		{"AI call logs", &model.AiCallLog{}},
		{"AI model cache", &model.AiModelCache{}},
		{"AI channels", &model.AiChannel{}},
	}
	for _, step := range steps {
		if err := deleteAIChannelRows(tx, step.value, channelIDs); err != nil {
			return fmt.Errorf("delete tenant %s: %w", step.name, err)
		}
	}
	return nil
}

func deleteAIChannelRows(tx *gorm.DB, value any, channelIDs []uint) error {
	column := "channel_id"
	if _, ok := value.(*model.AiChannel); ok {
		column = "id"
	}
	return deleteWhere(tx, value, column+" IN ?", channelIDs)
}

func reconcileAiProbeRows(tx *gorm.DB, result *tenantDeleteResult) error {
	var channelCount int64
	if err := tx.Model(&model.AiChannel{}).Count(&channelCount).Error; err != nil {
		return fmt.Errorf("count remaining AI channels: %w", err)
	}
	var ids []uint
	err := tx.Model(&model.Task{}).Where("type = ?", model.TaskTypeAiProbe).Pluck("id", &ids).Error
	if err != nil {
		return fmt.Errorf("load AI probe task: %w", err)
	}
	for _, id := range ids {
		if err := deleteTaskHistory(tx, id); err != nil {
			return err
		}
		if channelCount == 0 {
			if err := tx.Delete(&model.Task{}, id).Error; err != nil {
				return fmt.Errorf("delete AI probe task %d: %w", id, err)
			}
			result.deletedTaskIDs = append(result.deletedTaskIDs, id)
			continue
		}
		if err := resetTaskHistoryFields(tx, id); err != nil {
			return err
		}
	}
	return nil
}

func resetTaskHistoryFields(tx *gorm.DB, id uint) error {
	updates := map[string]any{"last_run_at": nil, "last_error": "", "run_count": 0}
	if err := tx.Model(&model.Task{}).Where("id = ?", id).Updates(updates).Error; err != nil {
		return fmt.Errorf("reset task %d history: %w", id, err)
	}
	return nil
}

func deleteTenantSnapshots(tx *gorm.DB, id uint) error {
	steps := []struct {
		name  string
		value any
	}{
		{"check snapshots", &model.CheckSnapshot{}},
		{"cost snapshots", &model.CostSnapshot{}},
		{"region cache", &model.RegionCache{}},
		{"compartment cache", &model.CompartmentCache{}},
	}
	for _, step := range steps {
		if err := deleteWhere(tx, step.value, "oci_config_id = ?", id); err != nil {
			return fmt.Errorf("delete tenant %s: %w", step.name, err)
		}
	}
	if err := tx.Where("key = ?", secretKey(id)).Delete(&model.Setting{}).Error; err != nil {
		return fmt.Errorf("delete tenant webhook secret: %w", err)
	}
	return nil
}

func deleteTenantConfig(tx *gorm.DB, id uint) error {
	res := tx.Delete(&model.OciConfig{}, id)
	if res.Error != nil {
		return fmt.Errorf("delete tenant row: %w", res.Error)
	}
	if res.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func deleteWhere(tx *gorm.DB, value any, query string, args ...any) error {
	return tx.Where(query, args...).Delete(value).Error
}

func (s *OciConfigService) afterTenantDelete(ctx context.Context, result *tenantDeleteResult) {
	// 数据已提交，客户端断开不应中断必需的运行时对齐。
	ctx = context.WithoutCancel(ctx)
	s.InvalidateAuditCache(result.config.ID)
	if client, ok := s.client.(tenancyCacheInvalidator); ok {
		client.InvalidateTenancy(result.config.TenancyOCID)
	}
	if s.cleanupTasks != nil {
		s.cleanupTasks.ApplyTenantCleanup(ctx, result.deletedTaskIDs, result.channelsGone)
	}
}
