package service

import (
	"context"
	"errors"
	"net/http"
	"strings"

	"gorm.io/gorm"

	"wucher/internal/domain/helicopter"
	sharedconst "wucher/internal/shared/const"
	"wucher/internal/shared/pkg/uuidv7"
)

type helicopterPINVerifier interface {
	ConsumeSecurityPINActionToken(ctx context.Context, userID []byte, action, token string) error
}

type helicopterPINSessionVerifier interface {
	AccessCookieName() string
	ParseAccessTokenSessionID(ctx context.Context, accessToken string) (string, error)
	ConsumeSecurityPINActionTokenInSession(ctx context.Context, userID []byte, action, token, sessionID string) error
}

type HelicopterWriteError struct {
	Status  int
	Title   string
	Detail  string
	Pointer string
}

func (e *HelicopterWriteError) Error() string {
	if e == nil {
		return ""
	}
	return e.Detail
}

type HelicopterWriteResult struct {
	Row                      *helicopter.Helicopter
	IsInUse                  bool
	PreviousFotoAttachmentID []byte
}

type HelicopterCreateInput struct {
	ID               []byte
	Designation      string
	Identifier       string
	Type             string
	SortKey          *int
	Engine1          string
	Engine2          string
	Consumption      float64
	NR1              bool
	NR2              bool
	LH               bool
	RH               bool
	MGB              bool
	IGB              bool
	TGB              bool
	UTCOffset        int
	FotoAttachmentID []byte
	Dry              bool
	AirOnGround      bool
	AOGReason        string
	AOGSource        string
	Note             string
	IsActive         bool
	ActorUserID      []byte
	PINVerifier      helicopterPINVerifier
	PINToken         string
	AccessToken      string
}

type HelicopterUpdateInput struct {
	ID                []byte
	Designation       *string
	Identifier        *string
	Type              *string
	SortKey           helicopter.SortKeyPatch
	Engine1           *string
	Engine2           *string
	Consumption       *float64
	NR1               *bool
	NR2               *bool
	LH                *bool
	RH                *bool
	MGB               *bool
	IGB               *bool
	TGB               *bool
	UTCOffset         *int
	FotoAttachmentID  []byte
	FotoAttachmentSet bool
	Dry               *bool
	AirOnGround       *bool
	AOGReason         *string
	AOGSource         *string
	Note              *string
	IsActive          *bool
	ActorUserID       []byte
	PINVerifier       helicopterPINVerifier
	PINToken          string
	AccessToken       string
}

func (s *HelicopterService) CreateDetailed(ctx context.Context, in HelicopterCreateInput) (*HelicopterWriteResult, error) {
	if s == nil || s.repo == nil {
		return nil, &HelicopterWriteError{Status: http.StatusInternalServerError, Title: "Create failed", Detail: "helicopter service is not configured"}
	}

	designation := strings.TrimSpace(in.Designation)
	identifier := normalizeHelicopterIdentifier(in.Identifier)
	heliType := strings.TrimSpace(in.Type)
	if designation == "" || identifier == "" || heliType == "" {
		return nil, &HelicopterWriteError{
			Status:  http.StatusUnprocessableEntity,
			Title:   "Validation error",
			Detail:  "designation, identifier, and type are required",
			Pointer: "/data/attributes",
		}
	}
	exists, err := s.repo.ExistsByIdentifier(ctx, identifier, nil)
	if err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Create failed", Detail: err.Error()}
	}
	if exists {
		return nil, &HelicopterWriteError{
			Status:  http.StatusConflict,
			Title:   "Conflict",
			Detail:  "Serial number already exists",
			Pointer: "/data/attributes/identifier",
		}
	}

	aogReason := strings.TrimSpace(in.AOGReason)
	if in.AirOnGround && aogReason == "" {
		return nil, &HelicopterWriteError{
			Status:  http.StatusUnprocessableEntity,
			Title:   "AOG reason required",
			Detail:  "AOG reason must be provided when AOG is active (`aog=true`).",
			Pointer: "/data/attributes/aog_reason",
		}
	}
	if in.AirOnGround {
		if err := verifyHelicopterAOGPIN(ctx, in.PINVerifier, in.AccessToken, in.ActorUserID, in.PINToken); err != nil {
			return nil, err
		}
	}

	row := &helicopter.Helicopter{
		ID:               append([]byte(nil), in.ID...),
		Designation:      designation,
		Identifier:       identifier,
		ReportSequence:   0,
		Type:             heliType,
		SortKey:          cloneIntPtr(in.SortKey),
		Engine1:          strings.TrimSpace(in.Engine1),
		Engine2:          strings.TrimSpace(in.Engine2),
		Consumption:      in.Consumption,
		NR1:              in.NR1,
		NR2:              in.NR2,
		LH:               in.LH,
		RH:               in.RH,
		MGB:              in.MGB,
		IGB:              in.IGB,
		TGB:              in.TGB,
		UTCOffset:        in.UTCOffset,
		FotoAttachmentID: append([]byte(nil), in.FotoAttachmentID...),
		Dry:              in.Dry,
		AirOnGround:      in.AirOnGround,
		AOGReason:        aogReason,
		AOGSource: func() string {
			if in.AirOnGround {
				return helicopter.AOGSourceManual
			}
			return helicopter.AOGSourceUnknown
		}(),
		Note:      strings.TrimSpace(in.Note),
		IsActive:  in.IsActive,
		CreatedBy: append([]byte(nil), in.ActorUserID...),
		UpdatedBy: append([]byte(nil), in.ActorUserID...),
	}
	if len(row.ID) == 0 {
		row.ID = uuidv7.MustBytes()
	}
	if err := s.Create(ctx, row); err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Create failed", Detail: err.Error()}
	}

	created, err := s.GetByID(ctx, row.ID)
	if err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Create failed", Detail: err.Error()}
	}
	if created == nil {
		created = row
	}
	isInUse, err := s.IsInUse(ctx, row.ID)
	if err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Create failed", Detail: err.Error()}
	}
	return &HelicopterWriteResult{Row: created, IsInUse: isInUse}, nil
}

func (s *HelicopterService) UpdateDetailed(ctx context.Context, in HelicopterUpdateInput) (*HelicopterWriteResult, error) {
	if s == nil || s.repo == nil {
		return nil, &HelicopterWriteError{Status: http.StatusInternalServerError, Title: "Update failed", Detail: "helicopter service is not configured"}
	}
	if len(in.ID) != 16 {
		return nil, &HelicopterWriteError{
			Status:  http.StatusUnprocessableEntity,
			Title:   "Validation error",
			Detail:  "id is invalid UUID",
			Pointer: "/path/id",
		}
	}

	existing, err := s.GetByID(ctx, in.ID)
	if err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: err.Error()}
	}
	if existing == nil {
		return nil, &HelicopterWriteError{Status: http.StatusNotFound, Title: "Not found", Detail: "helicopter not found"}
	}

	prevAOG := existing.AirOnGround
	prevFotoAttachmentID := append([]byte(nil), existing.FotoAttachmentID...)

	if in.Designation != nil {
		v := strings.TrimSpace(*in.Designation)
		if v == "" {
			return nil, &HelicopterWriteError{
				Status:  http.StatusUnprocessableEntity,
				Title:   "Validation error",
				Detail:  "designation cannot be empty",
				Pointer: "/data/attributes/designation",
			}
		}
		existing.Designation = v
	}
	if in.Identifier != nil {
		raw := normalizeHelicopterIdentifier(*in.Identifier)
		if raw == "" {
			return nil, &HelicopterWriteError{
				Status:  http.StatusUnprocessableEntity,
				Title:   "Validation error",
				Detail:  "identifier cannot be empty",
				Pointer: "/data/attributes/identifier",
			}
		}
		exists, existsErr := s.repo.ExistsByIdentifier(ctx, raw, existing.ID)
		if existsErr != nil {
			return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: existsErr.Error()}
		}
		if exists {
			return nil, &HelicopterWriteError{
				Status:  http.StatusConflict,
				Title:   "Conflict",
				Detail:  "Serial number already exists",
				Pointer: "/data/attributes/identifier",
			}
		}
		existing.Identifier = raw
	}
	if in.Type != nil {
		v := strings.TrimSpace(*in.Type)
		if v == "" {
			return nil, &HelicopterWriteError{
				Status:  http.StatusUnprocessableEntity,
				Title:   "Validation error",
				Detail:  "type cannot be empty",
				Pointer: "/data/attributes/type",
			}
		}
		existing.Type = v
	}
	s.ApplySortKeyPatch(existing, in.SortKey)
	if in.Engine1 != nil {
		existing.Engine1 = strings.TrimSpace(*in.Engine1)
	}
	if in.Engine2 != nil {
		existing.Engine2 = strings.TrimSpace(*in.Engine2)
	}
	if in.Consumption != nil {
		existing.Consumption = *in.Consumption
	}
	if in.NR1 != nil {
		existing.NR1 = *in.NR1
	}
	if in.NR2 != nil {
		existing.NR2 = *in.NR2
	}
	if in.LH != nil {
		existing.LH = *in.LH
	}
	if in.RH != nil {
		existing.RH = *in.RH
	}
	if in.MGB != nil {
		existing.MGB = *in.MGB
	}
	if in.IGB != nil {
		existing.IGB = *in.IGB
	}
	if in.TGB != nil {
		existing.TGB = *in.TGB
	}
	if in.UTCOffset != nil {
		existing.UTCOffset = *in.UTCOffset
	}
	if in.FotoAttachmentSet {
		if len(in.FotoAttachmentID) == 0 {
			existing.FotoAttachmentID = nil
		} else {
			existing.FotoAttachmentID = append([]byte(nil), in.FotoAttachmentID...)
		}
	}
	if in.Dry != nil {
		existing.Dry = *in.Dry
	}
	if in.AirOnGround != nil {
		existing.AirOnGround = *in.AirOnGround
		if *in.AirOnGround {
			existing.AOGSource = helicopter.AOGSourceManual
		}
	}
	if in.AOGReason != nil {
		existing.AOGReason = strings.TrimSpace(*in.AOGReason)
	}
	if in.AOGSource != nil {
		existing.AOGSource = strings.TrimSpace(*in.AOGSource)
	}
	if existing.AirOnGround && strings.TrimSpace(existing.AOGReason) == "" {
		return nil, &HelicopterWriteError{
			Status:  http.StatusUnprocessableEntity,
			Title:   "AOG reason required",
			Detail:  "AOG reason must be provided when AOG is active (`aog=true`).",
			Pointer: "/data/attributes/aog_reason",
		}
	}
	if in.AirOnGround != nil && *in.AirOnGround != prevAOG {
		if err := verifyHelicopterAOGPIN(ctx, in.PINVerifier, in.AccessToken, in.ActorUserID, in.PINToken); err != nil {
			return nil, err
		}
	}
	if in.Note != nil {
		existing.Note = strings.TrimSpace(*in.Note)
	}
	if in.IsActive != nil {
		existing.IsActive = *in.IsActive
	}
	existing.UpdatedBy = append([]byte(nil), in.ActorUserID...)

	if err := s.Update(ctx, existing); err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: err.Error()}
	}
	updated, err := s.GetByID(ctx, existing.ID)
	if err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: err.Error()}
	}
	if updated == nil {
		updated = existing
	}
	isInUse, err := s.IsInUse(ctx, existing.ID)
	if err != nil {
		return nil, &HelicopterWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: err.Error()}
	}
	return &HelicopterWriteResult{Row: updated, IsInUse: isInUse, PreviousFotoAttachmentID: prevFotoAttachmentID}, nil
}

func verifyHelicopterAOGPIN(
	ctx context.Context,
	verifier helicopterPINVerifier,
	accessToken string,
	userID []byte,
	token string,
) error {
	if verifier == nil {
		return &HelicopterWriteError{
			Status: http.StatusUnauthorized,
			Title:  "Unauthorized",
			Detail: "auth service not configured",
		}
	}
	if len(userID) == 0 {
		return &HelicopterWriteError{
			Status: http.StatusUnauthorized,
			Title:  "Unauthorized",
			Detail: "missing auth context",
		}
	}

	if sessionVerifier, ok := verifier.(helicopterPINSessionVerifier); ok {
		sessionID := ""
		if accessToken = strings.TrimSpace(accessToken); accessToken != "" {
			sid, err := sessionVerifier.ParseAccessTokenSessionID(ctx, accessToken)
			if err != nil {
				return &HelicopterWriteError{
					Status: http.StatusUnauthorized,
					Title:  "Unauthorized",
					Detail: "invalid or expired token",
				}
			}
			sessionID = sid
		}

		if err := sessionVerifier.ConsumeSecurityPINActionTokenInSession(ctx, userID, sharedconst.PinActionHelicopterAOGChange, token, sessionID); err != nil {
			return &HelicopterWriteError{
				Status:  http.StatusAccepted,
				Title:   "PIN verification required",
				Detail:  "valid PIN action token is required",
				Pointer: "/headers/X-PIN-Verification-Token",
			}
		}
		return nil
	}

	if err := verifier.ConsumeSecurityPINActionToken(ctx, userID, sharedconst.PinActionHelicopterAOGChange, token); err != nil {
		return &HelicopterWriteError{
			Status:  http.StatusAccepted,
			Title:   "PIN verification required",
			Detail:  "valid PIN action token is required",
			Pointer: "/headers/X-PIN-Verification-Token",
		}
	}
	return nil
}

func normalizeHelicopterIdentifier(raw string) string {
	return strings.ToUpper(strings.TrimSpace(raw))
}

func cloneIntPtr(v *int) *int {
	if v == nil {
		return nil
	}
	out := *v
	return &out
}

func isHelicopterNotFound(err error) bool {
	return errors.Is(err, gorm.ErrRecordNotFound)
}
