package mysql

import (
	"context"
	"errors"
	"strings"

	mysqldriver "github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	fmreport "wucher/internal/domain/fm_report"
)

type FMReportRepository struct {
	db *gorm.DB
}

func NewFMReportRepository(db *gorm.DB) *FMReportRepository {
	return &FMReportRepository{db: db}
}

func (r *FMReportRepository) MaxReportSeqByHelicopter(ctx context.Context, helicopterID []byte) (int, error) {
	if len(helicopterID) != 16 {
		return 0, nil
	}
	var maxSeq *int
	err := r.db.WithContext(ctx).
		Model(&fmreport.Report{}).
		Where("helicopter_id = ? AND report_code IS NOT NULL AND report_code <> '' AND deleted_at IS NULL", helicopterID).
		Select("MAX(CAST(SUBSTRING_INDEX(report_code, '-', -1) AS UNSIGNED))").
		Scan(&maxSeq).Error
	if err != nil {
		return 0, err
	}
	if maxSeq == nil {
		return 0, nil
	}
	return *maxSeq, nil
}

func (r *FMReportRepository) SetReportCode(ctx context.Context, id []byte, code string) error {
	if len(id) != 16 {
		return nil
	}
	err := r.db.WithContext(ctx).
		Model(&fmreport.Report{}).
		Where("id = ?", id).
		Update("report_code", code).Error
	if isDuplicateKeyError(err) {
		return fmreport.ErrDuplicateReportCode
	}
	return err
}

func isDuplicateKeyError(err error) bool {
	if err == nil {
		return false
	}
	if errors.Is(err, gorm.ErrDuplicatedKey) {
		return true
	}
	var mysqlErr *mysqldriver.MySQLError
	return errors.As(err, &mysqlErr) && mysqlErr.Number == 1062
}

func (r *FMReportRepository) Upsert(ctx context.Context, row *fmreport.Report) error {
	if row == nil {
		return nil
	}
	return r.db.WithContext(ctx).
		Clauses(clause.OnConflict{
			Columns: []clause.Column{{Name: "flight_id"}},
			DoUpdates: clause.AssignmentColumns([]string{
				"takeover_id",
				"flight_inspection_id",
				"engine1_gpc_n1",
				"engine1_ptc_n2",
				"engine2_gpc_n1",
				"engine2_ptc_n2",
				"completed_at",
				"completed_by",
				"updated_at",
				"updated_by",
			}),
		}).Create(row).Error
}

func (r *FMReportRepository) GetByFlightID(ctx context.Context, flightID []byte) (*fmreport.Report, error) {
	var row fmreport.Report
	err := r.db.WithContext(ctx).
		Where("flight_id = ? AND deleted_at IS NULL", flightID).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FMReportRepository) List(ctx context.Context, filter fmreport.ListFilter, limit, offset int) ([]fmreport.Report, int64, error) {
	rows := make([]fmreport.Report, 0)
	var total int64

	base := r.db.WithContext(ctx).
		Model(&fmreport.Report{}).
		Joins("JOIN flights f ON f.id = fm_reports.flight_id AND f.deleted_at IS NULL").
		Where("fm_reports.deleted_at IS NULL")

	if len(filter.FlightID) == 16 {
		base = base.Where("fm_reports.flight_id = ?", filter.FlightID)
	}
	if len(filter.TakeoverID) == 16 {
		base = base.Where("fm_reports.takeover_id = ?", filter.TakeoverID)
	}
	if len(filter.FlightInspectionID) == 16 {
		base = base.Where("fm_reports.flight_inspection_id = ?", filter.FlightInspectionID)
	}
	if strings.TrimSpace(filter.FlightDate) != "" {
		base = base.Where("f.date = ?", strings.TrimSpace(filter.FlightDate))
	}
	if strings.TrimSpace(filter.FromDate) != "" {
		base = base.Where("f.date >= ?", strings.TrimSpace(filter.FromDate))
	}
	if strings.TrimSpace(filter.ToDate) != "" {
		base = base.Where("f.date <= ?", strings.TrimSpace(filter.ToDate))
	}
	if strings.TrimSpace(filter.HelicopterIdentifier) != "" {
		base = base.
			Joins("LEFT JOIN takeover_acs ta ON ta.id = fm_reports.takeover_id AND ta.deleted_at IS NULL").
			Joins("LEFT JOIN reserve_acs ra ON ra.id = ta.reserve_ac_id AND ra.deleted_at IS NULL").
			Joins("LEFT JOIN helicopters heli ON heli.id = ra.helicopter_id AND heli.deleted_at IS NULL").
			Where("heli.identifier LIKE ?", "%"+strings.TrimSpace(filter.HelicopterIdentifier)+"%")
	}
	if strings.TrimSpace(filter.PilotName) != "" {
		like := "%" + strings.TrimSpace(filter.PilotName) + "%"
		base = base.Where(
			"EXISTS (SELECT 1 FROM takeover_roster_crews trc LEFT JOIN users trc_u ON trc_u.id = trc.user_id WHERE trc.takeover_id = fm_reports.takeover_id AND trc.role_code = 'PILOT' AND trc.deleted_at IS NULL AND (trc.name_label LIKE ? OR CONCAT(trc_u.first_name, ' ', trc_u.last_name) LIKE ?))",
			like, like,
		)
	}
	if strings.TrimSpace(filter.Search) != "" {
		like := "%" + strings.TrimSpace(filter.Search) + "%"
		base = base.Where("(f.mission_code LIKE ? OR COALESCE(fm_reports.engine1_gpc_n1, '') LIKE ? OR COALESCE(fm_reports.engine1_ptc_n2, '') LIKE ? OR COALESCE(fm_reports.engine2_gpc_n1, '') LIKE ? OR COALESCE(fm_reports.engine2_ptc_n2, '') LIKE ?)", like, like, like, like, like)
	}
	if filter.HasEngine1GpcN1 != nil {
		if *filter.HasEngine1GpcN1 {
			base = base.Where("fm_reports.engine1_gpc_n1 IS NOT NULL AND fm_reports.engine1_gpc_n1 <> ''")
		} else {
			base = base.Where("(fm_reports.engine1_gpc_n1 IS NULL OR fm_reports.engine1_gpc_n1 = '')")
		}
	}
	if filter.HasEngine1PtcN2 != nil {
		if *filter.HasEngine1PtcN2 {
			base = base.Where("fm_reports.engine1_ptc_n2 IS NOT NULL AND fm_reports.engine1_ptc_n2 <> ''")
		} else {
			base = base.Where("(fm_reports.engine1_ptc_n2 IS NULL OR fm_reports.engine1_ptc_n2 = '')")
		}
	}
	if filter.HasEngine2GpcN1 != nil {
		if *filter.HasEngine2GpcN1 {
			base = base.Where("fm_reports.engine2_gpc_n1 IS NOT NULL AND fm_reports.engine2_gpc_n1 <> ''")
		} else {
			base = base.Where("(fm_reports.engine2_gpc_n1 IS NULL OR fm_reports.engine2_gpc_n1 = '')")
		}
	}
	if filter.HasEngine2PtcN2 != nil {
		if *filter.HasEngine2PtcN2 {
			base = base.Where("fm_reports.engine2_ptc_n2 IS NOT NULL AND fm_reports.engine2_ptc_n2 <> ''")
		} else {
			base = base.Where("(fm_reports.engine2_ptc_n2 IS NULL OR fm_reports.engine2_ptc_n2 = '')")
		}
	}

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

	query := base.Order(fmReportListSortClause(filter.Sort))
	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 fmReportListSortClause(sort string) string {
	switch strings.ToLower(strings.TrimSpace(sort)) {
	case "flight_id":
		return "fm_reports.flight_id ASC"
	case "-flight_id":
		return "fm_reports.flight_id DESC"
	case "takeover_id":
		return "fm_reports.takeover_id ASC"
	case "-takeover_id":
		return "fm_reports.takeover_id DESC"
	case "flight_inspection_id":
		return "fm_reports.flight_inspection_id ASC"
	case "-flight_inspection_id":
		return "fm_reports.flight_inspection_id DESC"
	case "flight_date":
		return "f.date ASC"
	case "-flight_date":
		return "f.date DESC"
	case "created_at":
		return "fm_reports.created_at ASC"
	case "-created_at":
		return "fm_reports.created_at DESC"
	case "updated_at":
		return "fm_reports.updated_at ASC"
	case "-updated_at":
		return "fm_reports.updated_at DESC"
	case "engine1_gpc_n1":
		return "fm_reports.engine1_gpc_n1 ASC"
	case "-engine1_gpc_n1":
		return "fm_reports.engine1_gpc_n1 DESC"
	case "engine1_ptc_n2":
		return "fm_reports.engine1_ptc_n2 ASC"
	case "-engine1_ptc_n2":
		return "fm_reports.engine1_ptc_n2 DESC"
	case "engine2_gpc_n1":
		return "fm_reports.engine2_gpc_n1 ASC"
	case "-engine2_gpc_n1":
		return "fm_reports.engine2_gpc_n1 DESC"
	case "engine2_ptc_n2":
		return "fm_reports.engine2_ptc_n2 ASC"
	case "-engine2_ptc_n2":
		return "fm_reports.engine2_ptc_n2 DESC"
	default:
		return "f.date DESC, fm_reports.created_at DESC"
	}
}

func (r *FMReportRepository) GetByID(ctx context.Context, id []byte) (*fmreport.Report, error) {
	if len(id) != 16 {
		return nil, nil
	}
	var row fmreport.Report
	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 *FMReportRepository) Delete(ctx context.Context, id, deletedBy []byte) error {
	if len(id) != 16 {
		return nil
	}
	return r.db.WithContext(ctx).
		Model(&fmreport.Report{}).
		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 *FMReportRepository) CreateFleetHistory(ctx context.Context, row *fmreport.FleetHistory) error {
	if row == nil {
		return nil
	}
	return r.db.WithContext(ctx).
		Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "fm_report_id"}},
			DoNothing: true,
		}).Create(row).Error
}

func (r *FMReportRepository) GetFleetHistoryByReportID(ctx context.Context, reportID []byte) (*fmreport.FleetHistory, error) {
	var row fmreport.FleetHistory
	err := r.db.WithContext(ctx).
		Where("fm_report_id = ?", reportID).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}
