package mysql

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

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

	flightdata "wucher/internal/domain/flight_data"
	helicopterusage "wucher/internal/domain/helicopter_usage"
	"wucher/internal/shared/pkg/metricparse"
)

type HelicopterUsageRepository struct {
	db *gorm.DB
}

func NewHelicopterUsageRepository(db *gorm.DB) *HelicopterUsageRepository {
	return &HelicopterUsageRepository{db: db}
}

func (r *HelicopterUsageRepository) Create(ctx context.Context, row *helicopterusage.HelicopterUsage) error {
	return r.db.WithContext(ctx).Create(row).Error
}

func (r *HelicopterUsageRepository) Update(ctx context.Context, row *helicopterusage.HelicopterUsage) error {
	return r.db.WithContext(ctx).Save(row).Error
}

func (r *HelicopterUsageRepository) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
	if err := ensureNoReferenceBeforeDelete(ctx, r.db, "helicopter_usage", id); err != nil {
		return err
	}
	now := time.Now().UTC()
	updates := map[string]any{
		"deleted_at": now,
		"deleted_by": deletedBy,
		"updated_by": deletedBy,
	}
	return mapDeleteConstraintError(r.db.WithContext(ctx).
		Model(&helicopterusage.HelicopterUsage{}).
		Where("id = ? AND deleted_at IS NULL", id).
		Updates(updates).Error)
}

func (r *HelicopterUsageRepository) RefreshAll(ctx context.Context) error {
	now := time.Now().UTC()
	existing := map[string]helicopterusage.HelicopterUsage{}
	{
		var rows []helicopterusage.HelicopterUsage
		if err := r.db.WithContext(ctx).
			Select("helicopter_usage.*").
			Where("helicopter_usage.deleted_at IS NULL").
			Find(&rows).Error; err != nil {
			return err
		}
		for i := range rows {
			key, err := helicopterUsageKey(rows[i].HelicopterID)
			if err != nil {
				continue
			}
			existing[key] = rows[i]
		}
	}

	helicopterIDs, err := r.listActiveHelicopterIDs(ctx)
	if err != nil {
		return err
	}
	if len(helicopterIDs) == 0 {
		return nil
	}

	flightStats, err := r.flightStatsByHelicopter(ctx)
	if err != nil {
		return err
	}
	reportStats, err := r.reportStatsByHelicopter(ctx)
	if err != nil {
		return err
	}

	for _, helicopterID := range helicopterIDs {
		key, _ := helicopterUsageKey(helicopterID)
		row := &helicopterusage.HelicopterUsage{
			HelicopterID:         append([]byte(nil), helicopterID...),
			TotalLanding:         flightStats[key].TotalLanding,
			TotalAirframeHours:   flightStats[key].TotalAirframeHours,
			TotalAirframeCycles:  flightStats[key].TotalAirframeCycles,
			TotalEngine1Hours:    flightStats[key].TotalAirframeHours,
			TotalEngine2Hours:    flightStats[key].TotalAirframeHours,
			TotalEngine1GpcNgN1:  reportStats[key].TotalEngine1GpcNgN1,
			TotalEngine1PtcNfN2:  reportStats[key].TotalEngine1PtcNfN2,
			TotalEngine2GpcNgN1:  reportStats[key].TotalEngine2GpcNgN1,
			TotalEngine2PtcNfN2:  reportStats[key].TotalEngine2PtcNfN2,
			TotalFlightReport:    reportStats[key].TotalFlightReport,
			TotalHookRelease:     flightStats[key].TotalHookRelease,
			TotalRotorBrakeCycle: flightStats[key].TotalRotorBrakeCycle,
		}
		if ex, ok := existing[key]; ok {
			row.ID = ex.ID
			row.CreatedAt = ex.CreatedAt
			row.CreatedBy = ex.CreatedBy
			row.UpdatedBy = ex.UpdatedBy
			row.TotalEngine1Ccc = ex.TotalEngine1Ccc
			row.TotalEngine2Ccc = ex.TotalEngine2Ccc

			row.ManualAirframeHours = ex.ManualAirframeHours
			row.ManualAirframeCycles = ex.ManualAirframeCycles
			row.ManualEngine1Hours = ex.ManualEngine1Hours
			row.ManualEngine1GpcNgN1 = ex.ManualEngine1GpcNgN1
			row.ManualEngine1PtcNfN2 = ex.ManualEngine1PtcNfN2
			row.ManualEngine2Hours = ex.ManualEngine2Hours
			row.ManualEngine2GpcNgN1 = ex.ManualEngine2GpcNgN1
			row.ManualEngine2PtcNfN2 = ex.ManualEngine2PtcNfN2
			row.ManualLanding = ex.ManualLanding
			row.ManualFlightReport = ex.ManualFlightReport
			row.ManualHookRelease = ex.ManualHookRelease
			row.ManualRotorBrakeCycle = ex.ManualRotorBrakeCycle

			row.TotalAirframeHours += ex.ManualAirframeHours
			row.TotalAirframeCycles += ex.ManualAirframeCycles
			row.TotalEngine1Hours += ex.ManualEngine1Hours
			row.TotalEngine1GpcNgN1 += ex.ManualEngine1GpcNgN1
			row.TotalEngine1PtcNfN2 += ex.ManualEngine1PtcNfN2
			row.TotalEngine2Hours += ex.ManualEngine2Hours
			row.TotalEngine2GpcNgN1 += ex.ManualEngine2GpcNgN1
			row.TotalEngine2PtcNfN2 += ex.ManualEngine2PtcNfN2
			row.TotalLanding += ex.ManualLanding
			row.TotalFlightReport += ex.ManualFlightReport
			row.TotalHookRelease += ex.ManualHookRelease
			row.TotalRotorBrakeCycle += ex.ManualRotorBrakeCycle
		} else {
			row.CreatedAt = now
		}
		row.UpdatedAt = now
		if err := r.upsertByHelicopterID(ctx, row); err != nil {
			return err
		}
	}
	return nil
}

func (r *HelicopterUsageRepository) GetByID(ctx context.Context, id []byte) (*helicopterusage.HelicopterUsage, error) {
	var row helicopterusage.HelicopterUsage
	err := r.db.WithContext(ctx).
		Preload("Helicopter").
		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 *HelicopterUsageRepository) GetByHelicopterID(ctx context.Context, helicopterID []byte) (*helicopterusage.HelicopterUsage, error) {
	var row helicopterusage.HelicopterUsage
	err := r.db.WithContext(ctx).
		Preload("Helicopter").
		Where("helicopter_id = ? AND deleted_at IS NULL", helicopterID).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

type helicopterUsageFlightStats struct {
	TotalLanding         float64
	TotalAirframeHours   float64
	TotalAirframeCycles  float64
	TotalHookRelease     float64
	TotalRotorBrakeCycle float64
}

type helicopterUsageReportStats struct {
	TotalFlightReport   float64
	TotalEngine1GpcNgN1 float64
	TotalEngine1PtcNfN2 float64
	TotalEngine2GpcNgN1 float64
	TotalEngine2PtcNfN2 float64
}

func (r *HelicopterUsageRepository) listActiveHelicopterIDs(ctx context.Context) ([][]byte, error) {
	type row struct {
		ID []byte `gorm:"column:id"`
	}
	rows := make([]row, 0)
	if err := r.db.WithContext(ctx).
		Table("helicopters").
		Select("id").
		Where("is_active = ?", true).
		Order("created_at ASC, id ASC").
		Scan(&rows).Error; err != nil {
		return nil, err
	}
	out := make([][]byte, 0, len(rows))
	for i := range rows {
		if len(rows[i].ID) != 16 {
			continue
		}
		out = append(out, append([]byte(nil), rows[i].ID...))
	}
	return out, nil
}

func (r *HelicopterUsageRepository) flightStatsByHelicopter(ctx context.Context) (map[string]helicopterUsageFlightStats, error) {
	type row struct {
		HelicopterID         []byte `gorm:"column:helicopter_id"`
		TotalLanding         int64  `gorm:"column:total_landing"`
		TotalAirframeNS      int64  `gorm:"column:total_airframe_ns"`
		TotalAirframeCycles  int64  `gorm:"column:total_airframe_cycles"`
		TotalHookRelease     int64  `gorm:"column:total_hook_release"`
		TotalRotorBrakeCycle int64  `gorm:"column:total_rotor_brake_cycle"`
	}
	rows := make([]row, 0)
	err := r.db.WithContext(ctx).
		Table("flight_data fd").
		Select(`
			ra.helicopter_id,
			COALESCE(SUM(fd.landing_count), 0) AS total_landing,
			COALESCE(SUM(fd.duration), 0) AS total_airframe_ns,
			COUNT(fd.id) AS total_airframe_cycles,
			COALESCE(SUM(fd.hook_releases), 0) AS total_hook_release,
			COALESCE(SUM(fd.rotor_brake_cycle), 0) AS total_rotor_brake_cycle
		`).
		Joins("JOIN missions m ON m.id = fd.mission_id AND m.deleted_at IS NULL").
		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("fd.deleted_at IS NULL AND fd.status = ?", flightdata.StatusCompleted).
		Group("ra.helicopter_id").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	out := make(map[string]helicopterUsageFlightStats, len(rows))
	for i := range rows {
		key, err := helicopterUsageKey(rows[i].HelicopterID)
		if err != nil {
			continue
		}
		out[key] = helicopterUsageFlightStats{
			TotalLanding:         float64(rows[i].TotalLanding),
			TotalAirframeHours:   float64(rows[i].TotalAirframeNS) / float64(time.Hour),
			TotalAirframeCycles:  float64(rows[i].TotalAirframeCycles),
			TotalHookRelease:     float64(rows[i].TotalHookRelease),
			TotalRotorBrakeCycle: float64(rows[i].TotalRotorBrakeCycle),
		}
	}
	return out, nil
}

func (r *HelicopterUsageRepository) reportStatsByHelicopter(ctx context.Context) (map[string]helicopterUsageReportStats, error) {
	type row struct {
		HelicopterID []byte  `gorm:"column:helicopter_id"`
		Engine1GpcN1 *string `gorm:"column:engine1_gpc_n1"`
		Engine1PtcN2 *string `gorm:"column:engine1_ptc_n2"`
		Engine2GpcN1 *string `gorm:"column:engine2_gpc_n1"`
		Engine2PtcN2 *string `gorm:"column:engine2_ptc_n2"`
	}
	rows := make([]row, 0)
	if err := r.db.WithContext(ctx).
		Table("fm_reports").
		Select("helicopter_id, engine1_gpc_n1, engine1_ptc_n2, engine2_gpc_n1, engine2_ptc_n2").
		Where("deleted_at IS NULL AND helicopter_id IS NOT NULL").
		Order("helicopter_id ASC, created_at ASC, id ASC").
		Scan(&rows).Error; err != nil {
		return nil, err
	}
	out := make(map[string]helicopterUsageReportStats)
	for i := range rows {
		key, err := helicopterUsageKey(rows[i].HelicopterID)
		if err != nil {
			continue
		}
		acc := out[key]
		acc.TotalFlightReport++
		acc.TotalEngine1GpcNgN1 += parseNumericMetric(rows[i].Engine1GpcN1)
		acc.TotalEngine1PtcNfN2 += parseNumericMetric(rows[i].Engine1PtcN2)
		acc.TotalEngine2GpcNgN1 += parseNumericMetric(rows[i].Engine2GpcN1)
		acc.TotalEngine2PtcNfN2 += parseNumericMetric(rows[i].Engine2PtcN2)
		out[key] = acc
	}
	return out, nil
}

func (r *HelicopterUsageRepository) upsertByHelicopterID(ctx context.Context, row *helicopterusage.HelicopterUsage) error {
	return r.db.WithContext(ctx).
		Clauses(clause.OnConflict{
			Columns: []clause.Column{{Name: "helicopter_id"}},
			DoUpdates: clause.AssignmentColumns([]string{
				"total_landing",
				"total_airframe_hours",
				"total_airframe_cycles",
				"total_engine_1_hours",
				"total_engine_1_gpc_ng_n1",
				"total_engine_1_ptc_nf_n2",
				"total_engine_2_hours",
				"total_engine_2_gpc_ng_n1",
				"total_engine_2_ptc_nf_n2",
				"total_flight_report",
				"total_hook_release",
				"total_rotor_brake_cycle",
				"updated_at",
				"updated_by",
			}),
		}).
		Create(row).Error
}

func helicopterUsageKey(id []byte) (string, error) {
	if len(id) != 16 {
		return "", fmt.Errorf("invalid helicopter id")
	}
	return string(id), nil
}

func parseNumericMetric(v *string) float64 {
	return metricparse.Parse(v)
}

// FlightStatsByFlightID aggregates one flight's completed flight_data — the "today"
// contribution of an FM report. Uses the same SUM/COUNT definitions as the cumulative
// totals so prev = total - today reconciles exactly.
func (r *HelicopterUsageRepository) FlightStatsByFlightID(ctx context.Context, flightID []byte) (helicopterusage.FlightDayStats, error) {
	if len(flightID) != 16 {
		return helicopterusage.FlightDayStats{}, nil
	}
	var row struct {
		TotalLanding         int64 `gorm:"column:total_landing"`
		TotalAirframeNS      int64 `gorm:"column:total_airframe_ns"`
		TotalAirframeCycles  int64 `gorm:"column:total_airframe_cycles"`
		TotalHookRelease     int64 `gorm:"column:total_hook_release"`
		TotalRotorBrakeCycle int64 `gorm:"column:total_rotor_brake_cycle"`
	}
	err := r.db.WithContext(ctx).
		Table("flight_data fd").
		Select(`
			COALESCE(SUM(fd.landing_count), 0) AS total_landing,
			COALESCE(SUM(fd.duration), 0) AS total_airframe_ns,
			COUNT(fd.id) AS total_airframe_cycles,
			COALESCE(SUM(fd.hook_releases), 0) AS total_hook_release,
			COALESCE(SUM(fd.rotor_brake_cycle), 0) AS total_rotor_brake_cycle
		`).
		Joins("JOIN missions m ON m.id = fd.mission_id AND m.deleted_at IS NULL").
		Where("fd.deleted_at IS NULL AND fd.status = ? AND m.flight_id = ?", flightdata.StatusCompleted, flightID).
		Scan(&row).Error
	if err != nil {
		return helicopterusage.FlightDayStats{}, err
	}
	return helicopterusage.FlightDayStats{
		AirframeHours:   float64(row.TotalAirframeNS) / float64(time.Hour),
		AirframeCycles:  float64(row.TotalAirframeCycles),
		Landing:         float64(row.TotalLanding),
		HookRelease:     float64(row.TotalHookRelease),
		RotorBrakeCycle: float64(row.TotalRotorBrakeCycle),
	}, nil
}

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

	base := r.db.WithContext(ctx).
		Model(&helicopterusage.HelicopterUsage{}).
		Joins("LEFT JOIN helicopters h ON h.id = helicopter_usage.helicopter_id AND h.deleted_at IS NULL").
		Where("helicopter_usage.deleted_at IS NULL")

	if strings.TrimSpace(filter) != "" {
		like := "%" + strings.ToLower(strings.TrimSpace(filter)) + "%"
		base = base.Where(
			"LOWER(HEX(helicopter_usage.id)) LIKE ? OR LOWER(HEX(helicopter_usage.helicopter_id)) LIKE ? OR LOWER(h.designation) LIKE ? OR LOWER(h.identifier) LIKE ?",
			like, like, like, like,
		)
	}

	query := base
	if strings.TrimSpace(sort) != "" {
		query = query.Order(sort)
	} else {
		query = query.Order("helicopter_usage.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").Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	return rows, total, nil
}
