package mysql

import (
	"context"
	"errors"
	"time"

	"gorm.io/gorm"

	"wucher/internal/domain/flight"
)

type FlightRepository struct {
	db *gorm.DB
}

func preloadTakeoverRelations(q *gorm.DB) *gorm.DB {
	return q.
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.Base.OperationalShiftTimes").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople")
}

func NewFlightRepository(db *gorm.DB) *FlightRepository {
	return &FlightRepository{db: db}
}

func (r *FlightRepository) Create(ctx context.Context, row *flight.Flight) error {
	return r.db.WithContext(ctx).Create(row).Error
}

func (r *FlightRepository) GetLatestMissionCodeByPrefix(ctx context.Context, prefix string) (string, error) {
	var out struct {
		MissionCode string `gorm:"column:mission_code"`
	}
	err := r.db.WithContext(ctx).
		Table("flights").
		Select("mission_code").
		Where("mission_code LIKE ? AND deleted_at IS NULL", prefix+"%").
		Order("mission_code DESC").
		Limit(1).
		Scan(&out).Error
	if err != nil {
		return "", err
	}
	return out.MissionCode, nil
}

func (r *FlightRepository) Update(ctx context.Context, row *flight.Flight) error {
	return r.db.WithContext(ctx).Save(row).Error
}

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

func (r *FlightRepository) GetByID(ctx context.Context, id []byte) (*flight.Flight, error) {
	var row flight.Flight
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.Base.OperationalShiftTimes").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Where("flights.id = ? AND flights.deleted_at IS NULL", id).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FlightRepository) ListByUserID(ctx context.Context, userID []byte, limit, offset int) ([]flight.Flight, int64, error) {
	rows := make([]flight.Flight, 0)
	var total int64

	base := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Where("flights.deleted_at IS NULL").
		Where(
			`EXISTS (
				SELECT 1
				FROM duty_roster_crews c
				JOIN duty_rosters r ON r.id = c.roster_id
				WHERE c.user_id = ?
					AND c.deleted_at IS NULL
					AND r.deleted_at IS NULL
					AND r.flight_id = flights.id
			)`,
			userID,
		)

	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	query := base.Order("flights.date DESC, flights.created_at DESC")
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Find(&rows).Error; err != nil {
		return nil, 0, err
	}

	return rows, total, nil
}

func (r *FlightRepository) ListByCreatedBy(ctx context.Context, createdBy []byte, filter string, date string, sort string, limit, offset int) ([]flight.Flight, int64, error) {
	rows := make([]flight.Flight, 0)
	var total int64

	base := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Where("flights.deleted_at IS NULL").
		Where("flights.created_by = ?", createdBy)
	if filter != "" {
		like := "%" + filter + "%"
		base = base.Where("flights.mission_code LIKE ?", like)
	}
	if date != "" {
		base = base.Where("flights.date = ?", date)
	}

	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	query := base
	if sort != "" {
		query = query.Order(sort)
	} else {
		query = query.Order("flights.date DESC, flights.created_at DESC")
	}
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Find(&rows).Error; err != nil {
		return nil, 0, err
	}

	return rows, total, nil
}

func (r *FlightRepository) GetByReserveAcID(ctx context.Context, reserveAcID []byte) (*flight.Flight, error) {
	var row flight.Flight
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Joins("JOIN takeover_acs ta ON ta.id = flights.takeover_ac_id AND ta.deleted_at IS NULL").
		Where("ta.reserve_ac_id = ? AND flights.deleted_at IS NULL", reserveAcID).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FlightRepository) ListByReserveAcID(ctx context.Context, reserveAcID []byte) ([]flight.Flight, error) {
	rows := make([]flight.Flight, 0)
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Joins("JOIN takeover_acs ta ON ta.id = flights.takeover_ac_id AND ta.deleted_at IS NULL").
		Where("ta.reserve_ac_id = ? AND flights.deleted_at IS NULL", reserveAcID).
		Order("flights.created_at ASC, flights.id ASC").
		Find(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (r *FlightRepository) GetByTakeoverAcID(ctx context.Context, takeoverAcID []byte) (*flight.Flight, error) {
	var row flight.Flight
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Where("flights.takeover_ac_id = ? AND flights.deleted_at IS NULL", takeoverAcID).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FlightRepository) ListByTakeoverAcIDs(ctx context.Context, takeoverAcIDs [][]byte) ([]flight.Flight, error) {
	if len(takeoverAcIDs) == 0 {
		return []flight.Flight{}, nil
	}
	rows := make([]flight.Flight, 0)
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightListSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Where("flights.takeover_ac_id IN ? AND flights.deleted_at IS NULL", takeoverAcIDs).
		Find(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (r *FlightRepository) GetByDutyRosterID(ctx context.Context, dutyRosterID []byte) (*flight.Flight, error) {
	var row flight.Flight
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightListSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("JOIN duty_rosters dr ON dr.flight_id = flights.id AND dr.deleted_at IS NULL").
		Where("dr.id = ? AND flights.deleted_at IS NULL", dutyRosterID).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FlightRepository) ListByDutyRosterIDs(ctx context.Context, dutyRosterIDs [][]byte) ([]flight.Flight, error) {
	if len(dutyRosterIDs) == 0 {
		return []flight.Flight{}, nil
	}
	rows := make([]flight.Flight, 0)
	err := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightListSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Joins("JOIN duty_rosters dr ON dr.flight_id = flights.id AND dr.deleted_at IS NULL").
		Where("dr.id IN ? AND flights.deleted_at IS NULL", dutyRosterIDs).
		Find(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (r *FlightRepository) List(ctx context.Context, filter string, date string, sort string, limit, offset int) ([]flight.Flight, int64, error) {
	rows := make([]flight.Flight, 0)
	var total int64

	base := r.db.WithContext(ctx).
		Model(&flight.Flight{}).
		Select(flightReadSelectSQL()).
		Preload("Takeover").
		Preload("Takeover.Base").
		Preload("Takeover.ReserveAc").
		Preload("Takeover.ReserveAc.Aircraft").
		Preload("Takeover.ReserveAc.Inspection").
		Preload("Takeover.RosterCrews").
		Preload("Takeover.OtherPeople").
		Joins("LEFT JOIN users cu ON cu.id = flights.created_by").
		Joins("LEFT JOIN users uu ON uu.id = flights.updated_by").
		Where("flights.deleted_at IS NULL")
	if filter != "" {
		like := "%" + filter + "%"
		base = base.Where("flights.mission_code LIKE ?", like)
	}
	if date != "" {
		base = base.Where("flights.date = ?", date)
	}

	query := base
	if sort != "" {
		query = query.Order(sort)
	}
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	return rows, total, nil
}

func flightListSelectSQL() string {
	// created_by_name / updated_by_name are resolved via correlated subqueries so the list
	// queries carry the auditor display names without needing extra user JOINs (the read
	// query joins users; the list variants don't). Keeps get-all in parity with get-by-id.
	return `flights.*,
		(SELECT dr.id FROM duty_rosters dr WHERE dr.flight_id = flights.id AND dr.deleted_at IS NULL LIMIT 1) AS duty_roster_id,
		(SELECT ta.reserve_ac_id FROM takeover_acs ta WHERE ta.id = flights.takeover_ac_id AND ta.deleted_at IS NULL LIMIT 1) AS reserve_ac_id,
		COALESCE((SELECT NULLIF(TRIM(CONCAT(COALESCE(cu.first_name, ''), ' ', COALESCE(cu.last_name, ''))), '')
			FROM users cu WHERE cu.id = flights.created_by), '') AS created_by_name,
		COALESCE((SELECT NULLIF(TRIM(CONCAT(COALESCE(uu.first_name, ''), ' ', COALESCE(uu.last_name, ''))), '')
			FROM users uu WHERE uu.id = flights.updated_by), '') AS updated_by_name`
}

func flightReadSelectSQL() string {
	return `flights.*,
		(SELECT dr.id FROM duty_rosters dr WHERE dr.flight_id = flights.id AND dr.deleted_at IS NULL LIMIT 1) AS duty_roster_id,
		(SELECT ta.reserve_ac_id FROM takeover_acs ta WHERE ta.id = flights.takeover_ac_id AND ta.deleted_at IS NULL LIMIT 1) AS reserve_ac_id,
		COALESCE(
			NULLIF(TRIM(CONCAT(COALESCE(cu.first_name, ''), ' ', COALESCE(cu.last_name, ''))), ''),
			''
		) AS created_by_name,
		COALESCE(
			NULLIF(TRIM(CONCAT(COALESCE(uu.first_name, ''), ' ', COALESCE(uu.last_name, ''))), ''),
			''
		) AS updated_by_name`
}
