package mysql

import (
	"context"
	"errors"

	"gorm.io/gorm"

	complaint "wucher/internal/domain/complaint"
)

type ComplaintRepository struct {
	db *gorm.DB
}

func NewComplaintRepository(db *gorm.DB) *ComplaintRepository { return &ComplaintRepository{db: db} }

func (r *ComplaintRepository) Create(ctx context.Context, row *complaint.Complaint) error {
	return r.db.WithContext(ctx).Create(row).Error
}

func (r *ComplaintRepository) Update(ctx context.Context, row *complaint.Complaint) error {
	return r.db.WithContext(ctx).Save(row).Error
}

func (r *ComplaintRepository) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
	return r.db.WithContext(ctx).Model(&complaint.Complaint{}).Where("id = ? AND deleted_at IS NULL", id).Updates(map[string]any{"deleted_at": gorm.Expr("NOW(3)"), "deleted_by": deletedBy, "updated_by": deletedBy}).Error
}

func (r *ComplaintRepository) GetByID(ctx context.Context, id []byte) (*complaint.Complaint, error) {
	var row complaint.Complaint
	err := r.db.WithContext(ctx).
		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 *ComplaintRepository) ListByHelicopter(ctx context.Context, helicopterID []byte, limit, offset int) ([]complaint.Complaint, int64, error) {
	rows := make([]complaint.Complaint, 0)
	var total int64
	base := r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("helicopter_id = ? AND deleted_at IS NULL", helicopterID)
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	q := base.Order("reported_at DESC, created_at DESC")
	if limit > 0 {
		q = q.Limit(limit).Offset(offset)
	}
	err := q.Find(&rows).Error
	return rows, total, err
}

func (r *ComplaintRepository) List(ctx context.Context, filter complaint.ListFilter, sort string, limit, offset int) ([]complaint.Complaint, int64, error) {
	rows := make([]complaint.Complaint, 0)
	var total int64
	base := r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("complaints.deleted_at IS NULL")
	if len(filter.HelicopterID) == 16 {
		base = base.Where("complaints.helicopter_id = ?", filter.HelicopterID)
	}
	if len(filter.FlightID) == 16 {
		base = base.Where("complaints.flight_id = ?", filter.FlightID)
	}
	if filter.HoldItemsOnly {
		base = base.Where("complaints.fixed_at IS NULL AND complaints.mel_severity > ?", 0)
	}
	if filter.Search != "" {
		like := "%" + filter.Search + "%"
		base = base.Where("complaints.description LIKE ? OR CAST(complaints.mel_severity AS CHAR) LIKE ?", like, like)
	}
	if filter.DateFrom != "" {
		base = base.Where("complaints.reported_at >= ?", filter.DateFrom+" 00:00:00")
	}
	if filter.DateTo != "" {
		base = base.Where("complaints.reported_at <= ?", filter.DateTo+" 23:59:59.999")
	}
	if filter.Person != "" {
		like := "%" + filter.Person + "%"
		base = base.Where(
			"EXISTS (SELECT 1 FROM users u WHERE u.id IN (complaints.reported_by, complaints.mel_classified_by, complaints.nsr_decided_by) "+
				"AND TRIM(CONCAT(COALESCE(u.first_name, ''), ' ', COALESCE(u.last_name, ''))) LIKE ?)",
			like,
		)
	}
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	q := base
	if sort != "" {
		q = q.Order(sort)
	} else {
		q = q.Order("reported_at DESC, created_at DESC")
	}
	if limit > 0 {
		q = q.Limit(limit).Offset(offset)
	}
	if err := q.Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	return rows, total, nil
}

func (r *ComplaintRepository) HelicopterHasOpenComplaint(ctx context.Context, helicopterID []byte) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("helicopter_id = ? AND deleted_at IS NULL AND fixed_at IS NULL", helicopterID).
		Count(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

// OpenByHelicopterIDs returns the complaints the fleet-status Complaints column shows for
// the given helicopters, in one query (no N+1). That is every OPEN defect (fixed_at IS
// NULL) — including NSR-deferred and MEL-within-grace items, which are still open hold
// items — PLUS the FIXED defects from each helicopter's latest flight (the flight of its
// most recently reported complaint), so a just-resolved defect stays visible as the last
// state until a newer flight reports. Fixed defects from older flights drop off here but
// remain retrievable via the complaint history. A nil/empty helicopterIDs means "all".
func (r *ComplaintRepository) OpenByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) ([]complaint.Complaint, error) {
	rows := make([]complaint.Complaint, 0)
	q := r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("deleted_at IS NULL").
		Where("fixed_at IS NULL OR flight_id = (SELECT c2.flight_id FROM complaints c2 WHERE c2.helicopter_id = complaints.helicopter_id AND c2.deleted_at IS NULL AND c2.flight_id IS NOT NULL ORDER BY c2.reported_at DESC, c2.id DESC LIMIT 1)")
	if len(helicopterIDs) > 0 {
		q = q.Where("helicopter_id IN ?", helicopterIDs)
	}
	err := q.Order("reported_at DESC, created_at DESC").Find(&rows).Error
	return rows, err
}

func (r *ComplaintRepository) ActiveGroundingByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) ([]complaint.Complaint, error) {
	rows := make([]complaint.Complaint, 0)
	// Grounding candidates: open (not fixed), not released (not NSR), and already
	// classified (mel_classified_at set). A pending complaint is not yet a grounding
	// defect — it neither grounds the aircraft nor blocks the EASA release gate until it
	// is classified. action_taken is intentionally NOT excluded — recording corrective
	// action no longer lifts the grounding (only an NSR or a signed EASA release does),
	// so IsGrounding decides. A nil/empty helicopterIDs means "all helicopters".
	q := r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("deleted_at IS NULL AND is_nsr = 0 AND fixed_at IS NULL AND mel_classified_at IS NOT NULL")
	if len(helicopterIDs) > 0 {
		q = q.Where("helicopter_id IN ?", helicopterIDs)
	}
	err := q.Find(&rows).Error
	return rows, err
}

func (r *ComplaintRepository) HelicopterHasUnactionedComplaint(ctx context.Context, helicopterID []byte) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("helicopter_id = ? AND deleted_at IS NULL AND fixed_at IS NULL AND is_nsr = 0 AND (action_taken IS NULL OR action_taken = '')", helicopterID).
		Count(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *ComplaintRepository) MarkFixedByHelicopter(ctx context.Context, helicopterID []byte, easaID []byte, actor []byte) error {
	return r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("helicopter_id = ? AND deleted_at IS NULL AND fixed_at IS NULL", helicopterID).
		Updates(map[string]any{"fixed_at": gorm.Expr("NOW(3)"), "fixed_by_easa_id": easaID, "updated_by": actor}).Error
}

// MarkFixedByComplaint closes a single complaint (by id) — used when a release is
// scoped to one specific defect via complaint_id. No-op if already fixed/deleted.
func (r *ComplaintRepository) MarkFixedByComplaint(ctx context.Context, complaintID []byte, easaID []byte, actor []byte) error {
	return r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("id = ? AND deleted_at IS NULL AND fixed_at IS NULL", complaintID).
		Updates(map[string]any{"fixed_at": gorm.Expr("NOW(3)"), "fixed_by_easa_id": easaID, "updated_by": actor}).Error
}

// MarkFixedByFlight closes every open complaint reported on a flight — used when a
// release is scoped to a flight via flight_id. No-op for already fixed/deleted rows.
func (r *ComplaintRepository) MarkFixedByFlight(ctx context.Context, flightID []byte, easaID []byte, actor []byte) error {
	return r.db.WithContext(ctx).
		Model(&complaint.Complaint{}).
		Where("flight_id = ? AND deleted_at IS NULL AND fixed_at IS NULL", flightID).
		Updates(map[string]any{"fixed_at": gorm.Expr("NOW(3)"), "fixed_by_easa_id": easaID, "updated_by": actor}).Error
}
