package mysql

import (
	"context"
	"errors"
	"strings"
	"time"

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

	airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
	basedomain "wucher/internal/domain/base"
)

type AirRescuerChecklistRepository struct {
	db *gorm.DB
}

func NewAirRescuerChecklistRepository(db *gorm.DB) *AirRescuerChecklistRepository {
	return &AirRescuerChecklistRepository{db: db}
}

func (r *AirRescuerChecklistRepository) InTx(ctx context.Context, fn func(txRepo airrescuechecklist.TxRepository) error) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return fn(&AirRescuerChecklistRepository{db: tx})
	})
}

func (r *AirRescuerChecklistRepository) CreateChecklist(ctx context.Context, row *airrescuechecklist.AirRescuerChecklist) error {
	return r.db.WithContext(ctx).Create(row).Error
}

func (r *AirRescuerChecklistRepository) NextChecklistPosition(ctx context.Context, hemsBaseID []byte, scopeCode string) (int, error) {
	var maxPos int
	err := r.db.WithContext(ctx).
		Table("air_rescuer_checklists").
		Select("COALESCE(MAX(position), 0)").
		Where("hems_base_id = ? AND scope_code = ? AND deleted_at IS NULL", hemsBaseID, scopeCode).
		Scan(&maxPos).Error
	if err != nil {
		return 0, err
	}
	return maxPos + 1, nil
}

func (r *AirRescuerChecklistRepository) LockChecklistByID(ctx context.Context, id []byte) (*airrescuechecklist.AirRescuerChecklist, error) {
	row := &airrescuechecklist.AirRescuerChecklist{}
	if err := r.db.WithContext(ctx).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("id = ? AND deleted_at IS NULL", id).
		Take(row).Error; err != nil {
		return nil, err
	}
	return row, nil
}

func (r *AirRescuerChecklistRepository) UpdateChecklist(ctx context.Context, row *airrescuechecklist.AirRescuerChecklist) error {
	updates := map[string]any{
		"scope_code": row.ScopeCode,
		"title":      row.Title,
		"position":   row.Position,
		"updated_by": row.UpdatedBy,
	}
	res := r.db.WithContext(ctx).Model(&airrescuechecklist.AirRescuerChecklist{}).
		Where("id = ? AND deleted_at IS NULL", row.ID).
		Updates(updates)
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (r *AirRescuerChecklistRepository) SoftDeleteChecklist(ctx context.Context, id []byte, deletedBy []byte) error {
	now := time.Now().UTC()
	res := r.db.WithContext(ctx).Model(&airrescuechecklist.AirRescuerChecklist{}).
		Where("id = ? AND deleted_at IS NULL", id).
		Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy})
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (r *AirRescuerChecklistRepository) SoftDeleteItemsByChecklistID(ctx context.Context, checklistID []byte, deletedBy []byte) error {
	now := time.Now().UTC()
	return r.db.WithContext(ctx).Model(&airrescuechecklist.AirRescuerChecklistItem{}).
		Where("checklist_id = ? AND deleted_at IS NULL", checklistID).
		Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy}).Error
}

func (r *AirRescuerChecklistRepository) CreateChecklistItem(ctx context.Context, row *airrescuechecklist.AirRescuerChecklistItem) error {
	return r.db.WithContext(ctx).Create(row).Error
}

func (r *AirRescuerChecklistRepository) CreateChecklistItems(ctx context.Context, rows []airrescuechecklist.AirRescuerChecklistItem) error {
	if len(rows) == 0 {
		return nil
	}
	return r.db.WithContext(ctx).Create(&rows).Error
}

func (r *AirRescuerChecklistRepository) NextChecklistItemPosition(ctx context.Context, checklistID []byte) (int, error) {
	var maxPos int
	err := r.db.WithContext(ctx).
		Table("air_rescuer_checklist_items").
		Select("COALESCE(MAX(position), 0)").
		Where("checklist_id = ? AND deleted_at IS NULL", checklistID).
		Scan(&maxPos).Error
	if err != nil {
		return 0, err
	}
	return maxPos + 1, nil
}

func (r *AirRescuerChecklistRepository) LockChecklistItemByID(ctx context.Context, id []byte) (*airrescuechecklist.AirRescuerChecklistItem, error) {
	row := &airrescuechecklist.AirRescuerChecklistItem{}
	if err := r.db.WithContext(ctx).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("id = ? AND deleted_at IS NULL", id).
		Take(row).Error; err != nil {
		return nil, err
	}
	return row, nil
}

func (r *AirRescuerChecklistRepository) UpdateChecklistItem(ctx context.Context, row *airrescuechecklist.AirRescuerChecklistItem) error {
	updates := map[string]any{
		"name":       row.Name,
		"position":   row.Position,
		"updated_by": row.UpdatedBy,
	}
	res := r.db.WithContext(ctx).Model(&airrescuechecklist.AirRescuerChecklistItem{}).
		Where("id = ? AND deleted_at IS NULL", row.ID).
		Updates(updates)
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (r *AirRescuerChecklistRepository) SoftDeleteChecklistItem(ctx context.Context, id []byte, deletedBy []byte) error {
	now := time.Now().UTC()
	res := r.db.WithContext(ctx).Model(&airrescuechecklist.AirRescuerChecklistItem{}).
		Where("id = ? AND deleted_at IS NULL", id).
		Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy})
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (r *AirRescuerChecklistRepository) GetChecklistByID(ctx context.Context, id []byte) (*airrescuechecklist.ChecklistHeaderView, error) {
	out := &airrescuechecklist.ChecklistHeaderView{}
	err := r.db.WithContext(ctx).
		Table("air_rescuer_checklists c").
		Select(`
			c.id,
			c.hems_base_id,
			hb.base AS base_name,
			hb.base_abbreviation,
			bc.key AS base_category,
			c.scope_code,
			c.title,
			c.position,
			c.created_at,
			c.created_by,
			c.updated_at`).
		Joins("LEFT JOIN bases hb ON hb.id = c.hems_base_id").
		Joins("LEFT JOIN base_categories bc ON bc.id = hb.base_category_id").
		Where("c.id = ? AND c.deleted_at IS NULL", id).
		Where("hb.id IS NOT NULL").
		Take(out).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (r *AirRescuerChecklistRepository) GetChecklistItemByID(ctx context.Context, id []byte) (*airrescuechecklist.ChecklistItemView, error) {
	out := &airrescuechecklist.ChecklistItemView{}
	err := r.db.WithContext(ctx).
		Table("air_rescuer_checklist_items i").
		Select(`
			i.id,
			i.checklist_id,
			i.name,
			i.position,
			i.created_at,
			i.created_by,
			i.updated_at`).
		Joins("JOIN air_rescuer_checklists c ON c.id = i.checklist_id AND c.deleted_at IS NULL").
		Where("i.id = ? AND i.deleted_at IS NULL", id).
		Take(out).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (r *AirRescuerChecklistRepository) ListChecklistHeaders(ctx context.Context, filter airrescuechecklist.ChecklistHeaderFilter) ([]airrescuechecklist.ChecklistHeaderView, error) {
	rows := make([]airrescuechecklist.ChecklistHeaderView, 0)
	q := r.db.WithContext(ctx).
		Table("air_rescuer_checklists c").
		Select(`
			c.id,
			c.hems_base_id,
			hb.base AS base_name,
			hb.base_abbreviation,
			bc.key AS base_category,
			c.scope_code,
			c.title,
			c.position,
			c.created_at,
			c.created_by,
			c.updated_at`).
		Joins("LEFT JOIN bases hb ON hb.id = c.hems_base_id").
		Joins("LEFT JOIN base_categories bc ON bc.id = hb.base_category_id").
		Where("c.deleted_at IS NULL").
		Where("hb.id IS NOT NULL")

	if len(filter.HEMSBaseID) == 16 {
		q = q.Where("c.hems_base_id = ?", filter.HEMSBaseID)
	}
	if scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(filter.ScopeCode); scopeCode != "" {
		q = q.Where("UPPER(c.scope_code) = ?", scopeCode)
	}
	if categoryType, ok := basedomain.NormalizeCategoryType(filter.CategoryType); ok && strings.TrimSpace(filter.CategoryType) != "" {
		q = q.Where("bc.key = ?", categoryType)
	}
	q = applyChecklistBaseFilters(q, "hb", filter.BaseName, filter.BaseAbbreviation)

	err := q.Order("CASE WHEN hb.sortkey IS NULL THEN 1 ELSE 0 END ASC, hb.sortkey ASC, hb.base ASC, c.position ASC, c.title ASC").Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (r *AirRescuerChecklistRepository) ListChecklistItemsByChecklistIDs(ctx context.Context, checklistIDs [][]byte) ([]airrescuechecklist.ChecklistItemView, error) {
	if len(checklistIDs) == 0 {
		return []airrescuechecklist.ChecklistItemView{}, nil
	}
	rows := make([]airrescuechecklist.ChecklistItemView, 0)
	err := r.db.WithContext(ctx).
		Table("air_rescuer_checklist_items i").
		Select(`
			i.id,
			i.checklist_id,
			i.name,
			i.position,
			i.created_at,
			i.created_by,
			i.updated_at`).
		Joins("JOIN air_rescuer_checklists c ON c.id = i.checklist_id AND c.deleted_at IS NULL").
		Where("i.deleted_at IS NULL AND i.checklist_id IN ?", checklistIDs).
		Order("i.checklist_id ASC, i.position ASC, i.name ASC").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (r *AirRescuerChecklistRepository) ListHEMSBases(ctx context.Context, filter airrescuechecklist.HEMSBaseFilter) ([]airrescuechecklist.HEMSBaseView, error) {
	rows := make([]airrescuechecklist.HEMSBaseView, 0)
	q := r.db.WithContext(ctx).
		Table("bases b").
		Select(`
			b.id,
			b.base AS base_name,
			b.base_abbreviation,
			bc.key AS base_category,
			COALESCE(b.sortkey, 0) AS position`).
		Joins("JOIN base_categories bc ON bc.id = b.base_category_id").
		Where("b.is_active = ?", true).
		Where("b.checklist = ?", true)

	if len(filter.HEMSBaseID) == 16 {
		q = q.Where("b.id = ?", filter.HEMSBaseID)
	}
	if categoryType, ok := basedomain.NormalizeCategoryType(filter.CategoryType); ok && strings.TrimSpace(filter.CategoryType) != "" {
		q = q.Where("bc.key = ?", categoryType)
	}
	q = applyChecklistBaseFilters(q, "b", filter.BaseName, filter.BaseAbbreviation)

	if err := q.Order("CASE WHEN b.sortkey IS NULL THEN 1 ELSE 0 END ASC, b.sortkey ASC, b.base ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}
	return rows, nil
}

func applyChecklistBaseFilters(q *gorm.DB, alias, baseName, baseAbbreviation string) *gorm.DB {
	baseCol := "base"
	abbrCol := "base_abbreviation"
	if strings.TrimSpace(alias) != "" {
		baseCol = strings.TrimSpace(alias) + ".base"
		abbrCol = strings.TrimSpace(alias) + ".base_abbreviation"
	}
	if needle := strings.ToLower(strings.TrimSpace(baseName)); needle != "" {
		q = q.Where("LOWER("+baseCol+") LIKE ?", "%"+needle+"%")
	}
	if needle := strings.ToLower(strings.TrimSpace(baseAbbreviation)); needle != "" {
		q = q.Where("LOWER("+abbrCol+") LIKE ?", "%"+needle+"%")
	}
	return q
}
