package hr

import (
	"time"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/db"
)

// The sqlc row structs for an entity are structurally identical across queries
// (GetEmployeeByIDRow, ListEmployeesRow, ListActiveEmployeesRow,
// CreateEmployeeRow), so one converter per entity suffices; callers convert
// their row type explicitly: employeeFromRow(db.GetEmployeeByIDRow(row)).

func employeeFromRow(r db.GetEmployeeByIDRow) *Employee {
	return &Employee{
		ID:              r.ID,
		EmployeeNumber:  r.EmployeeNumber,
		UserID:          int8ToPtr(r.UserID),
		Name:            r.Name,
		NIK:             textOr(r.Nik),
		Department:      r.Department,
		JobLevel:        r.JobLevel,
		Position:        textOr(r.Position),
		EmploymentType:  r.EmploymentType,
		JoinDate:        dateToStrPtr(r.JoinDate),
		EndDate:         dateToStrPtr(r.EndDate),
		BasicSalary:           r.BasicSalary,
		Allowance:             r.Allowance,
		Phone:                 textOr(r.Phone),
		Email:                 textOr(r.Email),
		Address:               textOr(r.Address),
		City:                  textOr(r.City),
		BankName:              textOr(r.BankName),
		BankAccount:           textOr(r.BankAccount),
		BankAccountName:       textOr(r.BankAccountName),
		NPWP:                  textOr(r.Npwp),
		BpjsKetenagakerjaanNo: textOr(r.BpjsKetenagakerjaanNo),
		BpjsKesehatanNo:       textOr(r.BpjsKesehatanNo),
		Photo:                 textOr(r.Photo),
		IsActive:        r.IsActive,
		LeaveBalance:    r.LeaveBalance,
		LeaveUsed:       r.LeaveUsed,
		DeletedAt:       tsToPtr(r.DeletedAt),
	}
}

func attendanceFromRow(r db.GetAttendanceByIDRow) *Attendance {
	return &Attendance{
		ID:             r.ID,
		EmployeeID:     r.EmployeeID,
		AttendanceDate: r.AttendanceDate,
		ClockIn:        tsToPtr(r.ClockIn),
		ClockOut:       tsToPtr(r.ClockOut),
		ClockInLat:     ndToFloatPtr(r.ClockInLat),
		ClockInLng:     ndToFloatPtr(r.ClockInLng),
		ClockOutLat:    ndToFloatPtr(r.ClockOutLat),
		ClockOutLng:    ndToFloatPtr(r.ClockOutLng),
		Status:         r.Status,
		WorkHours:      ndToDecPtr(r.WorkHours),
		OvertimeHours:  ndToDecPtr(r.OvertimeHours),
		Notes:          textOr(r.Notes),
	}
}

func leaveFromRow(r db.GetLeaveByIDRow) *Leave {
	return &Leave{
		ID:         r.ID,
		EmployeeID: r.EmployeeID,
		LeaveType:  r.LeaveType,
		StartDate:  r.StartDate,
		EndDate:    r.EndDate,
		TotalDays:  int(r.TotalDays),
		Reason:     textOr(r.Reason),
		Status:     r.Status,
		ApprovedBy: int8ToPtr(r.ApprovedBy),
		ApprovedAt: tsToPtr(r.ApprovedAt),
		Notes:      textOr(r.Notes),
	}
}

func attendanceFromListRow(r db.ListAttendanceRow) *Attendance {
	a := attendanceFromRow(db.GetAttendanceByIDRow{
		ID: r.ID, EmployeeID: r.EmployeeID, AttendanceDate: r.AttendanceDate,
		ClockIn: r.ClockIn, ClockOut: r.ClockOut,
		ClockInLat: r.ClockInLat, ClockInLng: r.ClockInLng,
		ClockOutLat: r.ClockOutLat, ClockOutLng: r.ClockOutLng,
		Status: r.Status, WorkHours: r.WorkHours, OvertimeHours: r.OvertimeHours, Notes: r.Notes,
	})
	a.EmployeeName = r.EmployeeName
	a.Department = r.Department
	return a
}

func leaveFromListRow(r db.ListLeavesRow) *Leave {
	l := leaveFromRow(db.GetLeaveByIDRow{
		ID: r.ID, EmployeeID: r.EmployeeID, LeaveType: r.LeaveType,
		StartDate: r.StartDate, EndDate: r.EndDate, TotalDays: r.TotalDays,
		Reason: r.Reason, Status: r.Status, ApprovedBy: r.ApprovedBy,
		ApprovedAt: r.ApprovedAt, Notes: r.Notes,
	})
	l.EmployeeName = r.EmployeeName
	return l
}

func payrollFromRow(r db.GetPayrollByIDRow) *Payroll {
	var payDate *string
	if r.PaymentDate != "" {
		payDate = &r.PaymentDate
	}
	return &Payroll{
		ID:                  r.ID,
		PayrollNumber:       r.PayrollNumber,
		EmployeeID:          r.EmployeeID,
		PeriodMonth:         int(r.PeriodMonth),
		PeriodYear:          int(r.PeriodYear),
		BasicSalary:         r.BasicSalary,
		Allowances:          r.Allowances,
		OvertimePay:         r.OvertimePay,
		Bonus:               r.Bonus,
		Deductions:          r.Deductions,
		BpjsKetenagakerjaan: r.BpjsKetenagakerjaan,
		BpjsKesehatan:       r.BpjsKesehatan,
		Pph21:               r.Pph21,
		NetSalary:           r.NetSalary,
		PaymentDate:         payDate,
		Status:              r.Status,
		Notes:               textOr(r.Notes),
		GrossSalary:         r.BasicSalary.Add(r.Allowances).Add(r.OvertimePay).Add(r.Bonus),
	}
}

func payrollFromListRow(r db.ListPayrollsRow) *Payroll {
	p := payrollFromRow(db.GetPayrollByIDRow{
		ID: r.ID, PayrollNumber: r.PayrollNumber, EmployeeID: r.EmployeeID,
		PeriodMonth: r.PeriodMonth, PeriodYear: r.PeriodYear,
		BasicSalary: r.BasicSalary, Allowances: r.Allowances, OvertimePay: r.OvertimePay, Bonus: r.Bonus,
		Deductions: r.Deductions, BpjsKetenagakerjaan: r.BpjsKetenagakerjaan, BpjsKesehatan: r.BpjsKesehatan,
		Pph21: r.Pph21, NetSalary: r.NetSalary, PaymentDate: r.PaymentDate,
		Status: r.Status, Notes: r.Notes,
	})
	p.EmployeeName = r.EmployeeName
	return p
}

func performanceFromRow(r db.ListPerformancesRow) *Performance {
	return &Performance{
		ID:               r.ID,
		EmployeeID:       r.EmployeeID,
		EvaluatorID:      int8ToPtr(r.EvaluatorID),
		PeriodMonth:      int(r.PeriodMonth),
		PeriodYear:       int(r.PeriodYear),
		KPIScore:         ndOrZero(r.KpiScore),
		AttitudeScore:    ndOrZero(r.AttitudeScore),
		TeamworkScore:    ndOrZero(r.TeamworkScore),
		AchievementScore: ndOrZero(r.AchievementScore),
		TotalScore:       ndOrZero(r.TotalScore),
		Grade:            textOr(r.Grade),
		Achievements:     textOr(r.Achievements),
		Improvements:     textOr(r.Improvements),
		Notes:            textOr(r.Notes),
		EmployeeName:     r.EmployeeName,
		EvaluatorName:    r.EvaluatorName,
	}
}

func performanceFromGetRow(r db.GetPerformanceByIDRow) *Performance {
	return &Performance{
		ID:               r.ID,
		EmployeeID:       r.EmployeeID,
		EvaluatorID:      int8ToPtr(r.EvaluatorID),
		PeriodMonth:      int(r.PeriodMonth),
		PeriodYear:       int(r.PeriodYear),
		KPIScore:         ndOrZero(r.KpiScore),
		AttitudeScore:    ndOrZero(r.AttitudeScore),
		TeamworkScore:    ndOrZero(r.TeamworkScore),
		AchievementScore: ndOrZero(r.AchievementScore),
		TotalScore:       ndOrZero(r.TotalScore),
		Grade:            textOr(r.Grade),
		Achievements:     textOr(r.Achievements),
		Improvements:     textOr(r.Improvements),
		Notes:            textOr(r.Notes),
		EmployeeName:     r.EmployeeName,
		EvaluatorName:    r.EvaluatorName,
	}
}

// --- pgtype helpers ----------------------------------------------------------

func int8ToPtr(v pgtype.Int8) *int64 {
	if !v.Valid {
		return nil
	}
	x := v.Int64
	return &x
}

func int8OrNil(v *int64) pgtype.Int8 {
	if v == nil {
		return pgtype.Int8{}
	}
	return pgtype.Int8{Int64: *v, Valid: true}
}

func textOr(v pgtype.Text) string {
	if !v.Valid {
		return ""
	}
	return v.String
}

func textOrNil(v string) pgtype.Text {
	if v == "" {
		return pgtype.Text{}
	}
	return pgtype.Text{String: v, Valid: true}
}

func dateToStrPtr(v pgtype.Date) *string {
	if !v.Valid {
		return nil
	}
	s := v.Time.Format("2006-01-02")
	return &s
}

func dateOrNil(v string) pgtype.Date {
	if v == "" {
		return pgtype.Date{}
	}
	if t, err := time.Parse("2006-01-02", v); err == nil {
		return pgtype.Date{Time: t, Valid: true}
	}
	return pgtype.Date{}
}

func tsToPtr(v pgtype.Timestamptz) *time.Time {
	if !v.Valid {
		return nil
	}
	t := v.Time
	return &t
}

func tsToPgtz(v *time.Time) pgtype.Timestamptz {
	if v == nil {
		return pgtype.Timestamptz{}
	}
	return pgtype.Timestamptz{Time: *v, Valid: true}
}

func ndToFloatPtr(v decimal.NullDecimal) *float64 {
	if !v.Valid {
		return nil
	}
	f, _ := v.Decimal.Float64()
	return &f
}

func floatToNd(v *float64) decimal.NullDecimal {
	if v == nil {
		return decimal.NullDecimal{}
	}
	return decimal.NullDecimal{Decimal: decimal.NewFromFloat(*v), Valid: true}
}

func ndToDecPtr(v decimal.NullDecimal) *decimal.Decimal {
	if !v.Valid {
		return nil
	}
	d := v.Decimal
	return &d
}

func decToNdPtr(v *decimal.Decimal) decimal.NullDecimal {
	if v == nil {
		return decimal.NullDecimal{}
	}
	return decimal.NullDecimal{Decimal: *v, Valid: true}
}

func strVal(v *string) string {
	if v == nil {
		return ""
	}
	return *v
}

func decToNd(v decimal.Decimal) decimal.NullDecimal {
	return decimal.NullDecimal{Decimal: v, Valid: true}
}

func ndOrZero(v decimal.NullDecimal) decimal.Decimal {
	if !v.Valid {
		return decimal.Zero
	}
	return v.Decimal
}
