// Copyright (c) 2026 Alibaba Group and its affiliates

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdk

import (
	"log"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

const (
	websocketGatewayPingInterval = 30 * time.Second
	websocketGatewayPingTimeout  = 5 * time.Second
)

// Session represents a complete session with connection, session key and attestation
type Session struct {
	conn         *websocket.Conn
	sessionKey   []byte
	attestation  *AttestationResult
	serverPubKey string
	taskID       string
	done         chan struct{}
	closeOnce    sync.Once
}

// newSession creates a new session.
func newSession(conn *websocket.Conn, sessionKey []byte, attestation *AttestationResult, serverPubKey, taskID string) *Session {
	session := &Session{
		conn:         conn,
		sessionKey:   sessionKey,
		attestation:  attestation,
		serverPubKey: serverPubKey,
		taskID:       taskID,
		done:         make(chan struct{}),
	}
	if conn != nil {
		session.startGatewayKeepAlive()
	}
	return session
}

// Close closes the session's WebSocket connection and zeros sensitive data
func (s *Session) Close() error {
	var err error
	s.closeOnce.Do(func() {
		close(s.done)

		// Zero session key
		clear(s.sessionKey)
		s.sessionKey = nil

		if s.conn != nil {
			err = s.conn.Close()
		}
	})
	return err
}

func (s *Session) startGatewayKeepAlive() {
	go func() {
		ticker := time.NewTicker(websocketGatewayPingInterval)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				deadline := time.Now().Add(websocketGatewayPingTimeout)
				if err := s.conn.WriteControl(websocket.PingMessage, []byte("keepalive"), deadline); err != nil {
					log.Printf("[WARN] websocket gateway ping failed task_id=%s error=%v", s.taskID, err)
					return
				}
				log.Printf("[INFO] websocket gateway ping sent task_id=%s", s.taskID)
			case <-s.done:
				return
			}
		}
	}()
}
