package mysql

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

	"gorm.io/gorm"

	fleetstatus "wucher/internal/domain/fleet_status"
)

type FleetStatusRepository struct {
	db *gorm.DB
}

func NewFleetStatusRepository(db *gorm.DB) *FleetStatusRepository {
	return &FleetStatusRepository{db: db}
}

func (r *FleetStatusRepository) Create(ctx context.Context, row *fleetstatus.FleetStatus) error {
	return r.db.WithContext(ctx).Create(row).Error
}

func (r *FleetStatusRepository) CreateWithDB(ctx context.Context, db *gorm.DB, row *fleetstatus.FleetStatus) error {
	return db.WithContext(ctx).Create(row).Error
}

func (r *FleetStatusRepository) Update(ctx context.Context, row *fleetstatus.FleetStatus) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Model(&fleetstatus.FleetStatus{}).
			Where("id = ? AND deleted_at IS NULL", row.ID).
			Updates(map[string]any{
				"helicopter_id": row.HelicopterID,
				"status":        row.Status,
				"serviced_at":   row.ServicedAt,
				"updated_by":    row.UpdatedBy,
			}).Error; err != nil {
			return err
		}
		if err := tx.Where("fleet_status_id = ?", row.ID).Delete(&fleetstatus.MaintenanceSchedule{}).Error; err != nil {
			return err
		}
		if len(row.MaintenanceSchedules) > 0 {
			for i := range row.MaintenanceSchedules {
				row.MaintenanceSchedules[i].FleetStatusID = row.ID
			}
			if err := tx.Create(&row.MaintenanceSchedules).Error; err != nil {
				return err
			}
		}
		if err := tx.Where("fleet_status_id = ?", row.ID).Delete(&fleetstatus.FleetStatusFile{}).Error; err != nil {
			return err
		}
		if len(row.Files) > 0 {
			for i := range row.Files {
				row.Files[i].FleetStatusID = row.ID
			}
			if err := tx.Create(&row.Files).Error; err != nil {
				return err
			}
		}
		return nil
	})
}

func (r *FleetStatusRepository) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
	now := time.Now().UTC()
	return r.db.WithContext(ctx).
		Model(&fleetstatus.FleetStatus{}).
		Where("id = ? AND deleted_at IS NULL", id).
		Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy}).Error
}

func (r *FleetStatusRepository) GetByID(ctx context.Context, id []byte) (*fleetstatus.FleetStatus, error) {
	var row fleetstatus.FleetStatus
	err := r.db.WithContext(ctx).
		Preload("Helicopter").
		Preload("MaintenanceSchedules").
		Preload("Files").
		Preload("Files.Attachment").
		Preload("Files.Attachment.File").
		Where("id = ? AND deleted_at IS NULL", id).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FleetStatusRepository) List(ctx context.Context, filter, sort string, limit, offset int) ([]fleetstatus.FleetStatus, int64, error) {
	rows := make([]fleetstatus.FleetStatus, 0)
	var total int64
	base := r.db.WithContext(ctx).
		Model(&fleetstatus.FleetStatus{}).
		Where("deleted_at IS NULL")
	if strings.TrimSpace(filter) != "" {
		like := "%" + strings.ToLower(strings.TrimSpace(filter)) + "%"
		base = base.Where("LOWER(HEX(id)) LIKE ? OR LOWER(HEX(helicopter_id)) LIKE ?", like, like)
	}
	query := base
	if strings.TrimSpace(sort) != "" {
		query = query.Order(sort)
	} else {
		query = query.Order("created_at DESC")
	}
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Preload("Helicopter").
		Preload("MaintenanceSchedules").
		Preload("Files").
		Preload("Files.Attachment").
		Preload("Files.Attachment.File").
		Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	return rows, total, nil
}

func (r *FleetStatusRepository) MarkServiced(ctx context.Context, id []byte, servicedAt time.Time, actorID []byte) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var row fleetstatus.FleetStatus
		if err := tx.Preload("Helicopter").
			Preload("MaintenanceSchedules").
			Where("id = ? AND deleted_at IS NULL", id).
			First(&row).Error; err != nil {
			return err
		}

		if len(row.MaintenanceSchedules) > 0 {
			logs := make([]fleetstatus.FleetStatusServiceLog, 0, len(row.MaintenanceSchedules))
			for i := range row.MaintenanceSchedules {
				item := row.MaintenanceSchedules[i]
				logs = append(logs, fleetstatus.FleetStatusServiceLog{
					FleetStatusID:  row.ID,
					HelicopterID:   row.HelicopterID,
					InspectionType: item.InspectionType,
					Type:           item.Type,
					Due:            item.Due,
					Ext:            item.Ext,
					ServicedAt:     servicedAt,
					CreatedBy:      actorID,
				})
			}
			if err := tx.Create(&logs).Error; err != nil {
				return err
			}
		}

		if err := tx.Where("fleet_status_id = ?", row.ID).Delete(&fleetstatus.MaintenanceSchedule{}).Error; err != nil {
			return err
		}

		return tx.Model(&fleetstatus.FleetStatus{}).
			Where("id = ? AND deleted_at IS NULL", row.ID).
			Updates(map[string]any{
				"status":      fleetstatus.StatusServiced,
				"serviced_at": servicedAt,
				"updated_by":  actorID,
			}).
			Error
	})
}

func (r *FleetStatusRepository) LatestByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]*fleetstatus.FleetStatus, error) {
	out := make(map[string]*fleetstatus.FleetStatus)

	filtered := make([][]byte, 0, len(helicopterIDs))
	seen := make(map[string]struct{}, len(helicopterIDs))
	for _, id := range helicopterIDs {
		if len(id) != 16 {
			continue
		}
		key := string(id)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		filtered = append(filtered, append([]byte(nil), id...))
	}
	if len(filtered) == 0 {
		return out, nil
	}

	rows := make([]fleetstatus.FleetStatus, 0)
	// Ordered newest-first so the first row seen per helicopter is its latest.
	if err := r.db.WithContext(ctx).
		Where("deleted_at IS NULL").
		Where("helicopter_id IN ?", filtered).
		Order("created_at DESC, id DESC").
		Preload("Helicopter").
		Preload("MaintenanceSchedules").
		Preload("Files").
		Preload("Files.Attachment").
		Preload("Files.Attachment.File").
		Find(&rows).Error; err != nil {
		return nil, err
	}
	for i := range rows {
		key := string(rows[i].HelicopterID)
		if _, ok := out[key]; ok {
			continue
		}
		row := rows[i]
		out[key] = &row
	}
	return out, nil
}

func (r *FleetStatusRepository) ListServiceHistoryByHelicopterID(ctx context.Context, helicopterID []byte, limit, offset int) ([]fleetstatus.FleetStatusServiceLog, int64, error) {
	rows := make([]fleetstatus.FleetStatusServiceLog, 0)
	var total int64

	base := r.db.WithContext(ctx).
		Model(&fleetstatus.FleetStatusServiceLog{}).
		Where("helicopter_id = ?", helicopterID)
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	query := base.Order("serviced_at DESC, created_at DESC")
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Preload("Helicopter").Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	return rows, total, nil
}

func (r *FleetStatusRepository) HelicopterIDsWithFleetStatus(ctx context.Context) ([][]byte, error) {
	type rowT struct {
		HelicopterID []byte `gorm:"column:helicopter_id"`
	}
	rows := make([]rowT, 0)
	err := r.db.WithContext(ctx).
		Table("fleet_statuses").
		Select("DISTINCT helicopter_id").
		Where("deleted_at IS NULL").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	out := make([][]byte, 0, len(rows))
	for i := range rows {
		if len(rows[i].HelicopterID) == 16 {
			out = append(out, rows[i].HelicopterID)
		}
	}
	return out, nil
}

func (r *FleetStatusRepository) HelicopterIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error) {
	if len(missionID) != 16 {
		return nil, nil
	}
	var out struct {
		HelicopterID []byte `gorm:"column:helicopter_id"`
	}
	err := r.db.WithContext(ctx).
		Table("missions m").
		Select("ra.helicopter_id AS helicopter_id").
		Joins("JOIN flights f ON f.id = m.flight_id AND f.deleted_at IS NULL").
		Joins("JOIN takeover_acs ta ON ta.id = f.takeover_ac_id AND ta.deleted_at IS NULL").
		Joins("JOIN reserve_acs ra ON ra.id = ta.reserve_ac_id AND ra.deleted_at IS NULL").
		Where("m.id = ? AND m.deleted_at IS NULL", missionID).
		Limit(1).
		Scan(&out).Error
	if err != nil {
		return nil, err
	}
	return out.HelicopterID, nil
}

