package jitsi

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/google/uuid"
)

// JitsiService handles Jitsi Meet integration for video conferences
type JitsiService struct {
	baseURL    string
	appID      string
	appSecret  string
	httpClient *http.Client
}

// Config holds Jitsi service configuration
type Config struct {
	BaseURL   string // e.g., "http://localhost:8443"
	AppID     string // Application ID for JWT (optional)
	AppSecret string // Secret for JWT signing (optional)
}

// NewJitsiService creates a new Jitsi service instance
func NewJitsiService(cfg Config) *JitsiService {
	return &JitsiService{
		baseURL:   strings.TrimSuffix(cfg.BaseURL, "/"),
		appID:     cfg.AppID,
		appSecret: cfg.AppSecret,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// ========================================
// Types
// ========================================

// Meeting represents a Jitsi meeting configuration
type Meeting struct {
	RoomName    string            `json:"room_name"`
	DisplayName string            `json:"display_name,omitempty"`
	Email       string            `json:"email,omitempty"`
	Avatar      string            `json:"avatar,omitempty"`
	Subject     string            `json:"subject,omitempty"`
	Password    string            `json:"password,omitempty"`
	StartTime   *time.Time        `json:"start_time,omitempty"`
	Duration    int               `json:"duration,omitempty"` // in minutes
	Config      *MeetingConfig    `json:"config,omitempty"`
	Moderator   bool              `json:"moderator,omitempty"`
	Features    *MeetingFeatures  `json:"features,omitempty"`
}

// MeetingConfig holds Jitsi room configuration options
type MeetingConfig struct {
	StartWithAudioMuted bool `json:"start_with_audio_muted,omitempty"`
	StartWithVideoMuted bool `json:"start_with_video_muted,omitempty"`
	DisableDeepLinking  bool `json:"disable_deep_linking,omitempty"`
	RequireDisplayName  bool `json:"require_display_name,omitempty"`
	EnableLobby         bool `json:"enable_lobby,omitempty"`
	EnableRecording     bool `json:"enable_recording,omitempty"`
}

// MeetingFeatures controls which features are enabled
type MeetingFeatures struct {
	Livestreaming bool `json:"livestreaming,omitempty"`
	Recording     bool `json:"recording,omitempty"`
	Transcription bool `json:"transcription,omitempty"`
	OutboundCall  bool `json:"outbound_call,omitempty"`
}

// MeetingLink contains the generated meeting URL and metadata
type MeetingLink struct {
	URL          string     `json:"url"`
	RoomName     string     `json:"room_name"`
	JoinURL      string     `json:"join_url"`
	ModeratorURL string     `json:"moderator_url,omitempty"`
	Password     string     `json:"password,omitempty"`
	ExpiresAt    *time.Time `json:"expires_at,omitempty"`
	JWT          string     `json:"jwt,omitempty"`
}

// JWTClaims represents the JWT payload for Jitsi
type JWTClaims struct {
	Audience  string       `json:"aud,omitempty"`
	Issuer    string       `json:"iss,omitempty"`
	Subject   string       `json:"sub,omitempty"`
	Room      string       `json:"room,omitempty"`
	ExpiresAt int64        `json:"exp,omitempty"`
	NotBefore int64        `json:"nbf,omitempty"`
	Context   *JWTContext  `json:"context,omitempty"`
	Moderator bool         `json:"moderator,omitempty"`
	Features  *JWTFeatures `json:"features,omitempty"`
}

// JWTContext contains user information for JWT
type JWTContext struct {
	User   *JWTUser   `json:"user,omitempty"`
	Group  string     `json:"group,omitempty"`
	Callee *JWTCallee `json:"callee,omitempty"`
}

// JWTUser represents user info in JWT
type JWTUser struct {
	ID          string `json:"id,omitempty"`
	Name        string `json:"name,omitempty"`
	Email       string `json:"email,omitempty"`
	Avatar      string `json:"avatar,omitempty"`
	Moderator   bool   `json:"moderator,omitempty"`
	HiddenFromRecorder bool `json:"hidden-from-recorder,omitempty"`
}

// JWTCallee represents callee info (for 1:1 calls)
type JWTCallee struct {
	ID     string `json:"id,omitempty"`
	Name   string `json:"name,omitempty"`
	Avatar string `json:"avatar,omitempty"`
}

// JWTFeatures controls JWT-based feature access
type JWTFeatures struct {
	Livestreaming string `json:"livestreaming,omitempty"` // "true" or "false"
	Recording     string `json:"recording,omitempty"`
	Transcription string `json:"transcription,omitempty"`
	OutboundCall  string `json:"outbound-call,omitempty"`
}

// ScheduledMeeting represents a scheduled training/meeting
type ScheduledMeeting struct {
	ID          string     `json:"id"`
	Title       string     `json:"title"`
	Description string     `json:"description,omitempty"`
	RoomName    string     `json:"room_name"`
	HostID      string     `json:"host_id"`
	HostName    string     `json:"host_name"`
	StartTime   time.Time  `json:"start_time"`
	EndTime     time.Time  `json:"end_time"`
	Duration    int        `json:"duration"` // in minutes
	Password    string     `json:"password,omitempty"`
	MaxParticipants int    `json:"max_participants,omitempty"`
	Features    *MeetingFeatures `json:"features,omitempty"`
	CreatedAt   time.Time  `json:"created_at"`
	UpdatedAt   time.Time  `json:"updated_at"`
}

// ========================================
// Meeting Management
// ========================================

// CreateMeetingLink generates a meeting URL with optional JWT authentication
func (s *JitsiService) CreateMeetingLink(ctx context.Context, meeting Meeting) (*MeetingLink, error) {
	// Generate room name if not provided
	roomName := meeting.RoomName
	if roomName == "" {
		roomName = s.generateRoomName()
	}

	// Sanitize room name (Jitsi-compatible)
	roomName = s.sanitizeRoomName(roomName)

	link := &MeetingLink{
		RoomName: roomName,
		URL:      fmt.Sprintf("%s/%s", s.baseURL, roomName),
		JoinURL:  fmt.Sprintf("%s/%s", s.baseURL, roomName),
		Password: meeting.Password,
	}

	// Generate JWT if authentication is configured
	if s.appSecret != "" {
		jwt, expiresAt, err := s.generateJWT(meeting, roomName)
		if err != nil {
			return nil, fmt.Errorf("failed to generate JWT: %w", err)
		}
		link.JWT = jwt
		link.ExpiresAt = expiresAt
		link.JoinURL = fmt.Sprintf("%s/%s?jwt=%s", s.baseURL, roomName, jwt)

		// Generate moderator URL if user is moderator
		if meeting.Moderator {
			link.ModeratorURL = link.JoinURL
		}
	}

	// Add config parameters to URL
	if meeting.Config != nil {
		params := s.buildConfigParams(meeting.Config)
		if params != "" {
			separator := "?"
			if strings.Contains(link.JoinURL, "?") {
				separator = "&"
			}
			link.JoinURL += separator + params
		}
	}

	return link, nil
}

// CreateTrainingSession creates a meeting link optimized for training sessions
func (s *JitsiService) CreateTrainingSession(ctx context.Context, title string, hostName string, hostEmail string, duration int) (*MeetingLink, error) {
	meeting := Meeting{
		RoomName:    s.generateTrainingRoomName(title),
		DisplayName: hostName,
		Email:       hostEmail,
		Subject:     title,
		Duration:    duration,
		Moderator:   true,
		Config: &MeetingConfig{
			StartWithAudioMuted: true,  // Participants start muted
			StartWithVideoMuted: false, // Video on for training
			RequireDisplayName:  true,  // Know who's attending
			EnableLobby:         true,  // Waiting room
			EnableRecording:     true,  // Allow recording
		},
		Features: &MeetingFeatures{
			Recording:     true,
			Transcription: false,
		},
	}

	return s.CreateMeetingLink(ctx, meeting)
}

// CreateQuickMeeting creates a simple ad-hoc meeting
func (s *JitsiService) CreateQuickMeeting(ctx context.Context, displayName string) (*MeetingLink, error) {
	meeting := Meeting{
		DisplayName: displayName,
		Config: &MeetingConfig{
			StartWithAudioMuted: false,
			StartWithVideoMuted: false,
		},
	}

	return s.CreateMeetingLink(ctx, meeting)
}

// CreateParentTeacherMeeting creates a meeting for parent-teacher conferences
func (s *JitsiService) CreateParentTeacherMeeting(ctx context.Context, teacherName string, parentName string, studentName string, scheduledTime time.Time) (*MeetingLink, error) {
	roomName := fmt.Sprintf("elterngespraech-%s-%s",
		s.sanitizeRoomName(studentName),
		scheduledTime.Format("20060102-1504"))

	meeting := Meeting{
		RoomName:    roomName,
		DisplayName: teacherName,
		Subject:     fmt.Sprintf("Elterngespräch: %s", studentName),
		StartTime:   &scheduledTime,
		Duration:    30, // 30 minutes default
		Moderator:   true,
		Password:    s.generatePassword(),
		Config: &MeetingConfig{
			StartWithAudioMuted: false,
			StartWithVideoMuted: false,
			RequireDisplayName:  true,
			EnableLobby:         true, // Teacher admits parent
			DisableDeepLinking:  true,
		},
	}

	return s.CreateMeetingLink(ctx, meeting)
}

// CreateClassMeeting creates a meeting for an entire class
func (s *JitsiService) CreateClassMeeting(ctx context.Context, className string, teacherName string, subject string) (*MeetingLink, error) {
	roomName := fmt.Sprintf("klasse-%s-%s",
		s.sanitizeRoomName(className),
		time.Now().Format("20060102"))

	meeting := Meeting{
		RoomName:    roomName,
		DisplayName: teacherName,
		Subject:     fmt.Sprintf("%s - %s", className, subject),
		Moderator:   true,
		Config: &MeetingConfig{
			StartWithAudioMuted: true,  // Students muted by default
			StartWithVideoMuted: false,
			RequireDisplayName:  true,
			EnableLobby:         false, // Direct join for classes
		},
	}

	return s.CreateMeetingLink(ctx, meeting)
}

// ========================================
// JWT Generation
// ========================================

// generateJWT creates a signed JWT for Jitsi authentication
func (s *JitsiService) generateJWT(meeting Meeting, roomName string) (string, *time.Time, error) {
	if s.appSecret == "" {
		return "", nil, fmt.Errorf("app secret not configured")
	}

	now := time.Now()

	// Default expiration: 24 hours or based on meeting duration
	expiration := now.Add(24 * time.Hour)
	if meeting.Duration > 0 {
		expiration = now.Add(time.Duration(meeting.Duration+30) * time.Minute)
	}
	if meeting.StartTime != nil {
		expiration = meeting.StartTime.Add(time.Duration(meeting.Duration+60) * time.Minute)
	}

	claims := JWTClaims{
		Audience:  "jitsi",
		Issuer:    s.appID,
		Subject:   "meet.jitsi",
		Room:      roomName,
		ExpiresAt: expiration.Unix(),
		NotBefore: now.Add(-5 * time.Minute).Unix(), // 5 min grace period
		Moderator: meeting.Moderator,
		Context: &JWTContext{
			User: &JWTUser{
				ID:        uuid.New().String(),
				Name:      meeting.DisplayName,
				Email:     meeting.Email,
				Avatar:    meeting.Avatar,
				Moderator: meeting.Moderator,
			},
		},
	}

	// Add features if specified
	if meeting.Features != nil {
		claims.Features = &JWTFeatures{
			Recording:     boolToString(meeting.Features.Recording),
			Livestreaming: boolToString(meeting.Features.Livestreaming),
			Transcription: boolToString(meeting.Features.Transcription),
			OutboundCall:  boolToString(meeting.Features.OutboundCall),
		}
	}

	// Create JWT
	token, err := s.signJWT(claims)
	if err != nil {
		return "", nil, err
	}

	return token, &expiration, nil
}

// signJWT creates and signs a JWT token
func (s *JitsiService) signJWT(claims JWTClaims) (string, error) {
	// Header
	header := map[string]string{
		"alg": "HS256",
		"typ": "JWT",
	}

	headerJSON, err := json.Marshal(header)
	if err != nil {
		return "", err
	}

	// Payload
	payloadJSON, err := json.Marshal(claims)
	if err != nil {
		return "", err
	}

	// Encode
	headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)
	payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)

	// Sign
	message := headerB64 + "." + payloadB64
	h := hmac.New(sha256.New, []byte(s.appSecret))
	h.Write([]byte(message))
	signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil))

	return message + "." + signature, nil
}

// ========================================
// Health Check
// ========================================

// HealthCheck verifies the Jitsi server is accessible
func (s *JitsiService) HealthCheck(ctx context.Context) error {
	req, err := http.NewRequestWithContext(ctx, "GET", s.baseURL, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := s.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("jitsi server unreachable: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 500 {
		return fmt.Errorf("jitsi server error: status %d", resp.StatusCode)
	}

	return nil
}

// GetServerInfo returns information about the Jitsi server
func (s *JitsiService) GetServerInfo() map[string]string {
	return map[string]string{
		"base_url":       s.baseURL,
		"app_id":         s.appID,
		"auth_enabled":   boolToString(s.appSecret != ""),
	}
}

// ========================================
// URL Building
// ========================================

// BuildEmbedURL creates an embeddable iframe URL
func (s *JitsiService) BuildEmbedURL(roomName string, displayName string, config *MeetingConfig) string {
	params := url.Values{}

	if displayName != "" {
		params.Set("userInfo.displayName", displayName)
	}

	if config != nil {
		if config.StartWithAudioMuted {
			params.Set("config.startWithAudioMuted", "true")
		}
		if config.StartWithVideoMuted {
			params.Set("config.startWithVideoMuted", "true")
		}
		if config.DisableDeepLinking {
			params.Set("config.disableDeepLinking", "true")
		}
	}

	embedURL := fmt.Sprintf("%s/%s", s.baseURL, s.sanitizeRoomName(roomName))
	if len(params) > 0 {
		embedURL += "#" + params.Encode()
	}

	return embedURL
}

// BuildIFrameCode generates HTML iframe code for embedding
func (s *JitsiService) BuildIFrameCode(roomName string, width int, height int) string {
	if width == 0 {
		width = 800
	}
	if height == 0 {
		height = 600
	}

	return fmt.Sprintf(
		`<iframe src="%s/%s" width="%d" height="%d" allow="camera; microphone; fullscreen; display-capture; autoplay" style="border: 0;"></iframe>`,
		s.baseURL,
		s.sanitizeRoomName(roomName),
		width,
		height,
	)
}

// ========================================
// Helper Functions
// ========================================

// generateRoomName creates a unique room name
func (s *JitsiService) generateRoomName() string {
	return fmt.Sprintf("breakpilot-%s", uuid.New().String()[:8])
}

// generateTrainingRoomName creates a room name for training sessions
func (s *JitsiService) generateTrainingRoomName(title string) string {
	sanitized := s.sanitizeRoomName(title)
	if sanitized == "" {
		sanitized = "schulung"
	}
	return fmt.Sprintf("%s-%s", sanitized, time.Now().Format("20060102-1504"))
}

// sanitizeRoomName removes invalid characters from room names
func (s *JitsiService) sanitizeRoomName(name string) string {
	// Replace spaces and special characters
	result := strings.ToLower(name)
	result = strings.ReplaceAll(result, " ", "-")
	result = strings.ReplaceAll(result, "ä", "ae")
	result = strings.ReplaceAll(result, "ö", "oe")
	result = strings.ReplaceAll(result, "ü", "ue")
	result = strings.ReplaceAll(result, "ß", "ss")

	// Remove any remaining non-alphanumeric characters except hyphen
	var cleaned strings.Builder
	for _, r := range result {
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
			cleaned.WriteRune(r)
		}
	}

	// Remove consecutive hyphens
	result = cleaned.String()
	for strings.Contains(result, "--") {
		result = strings.ReplaceAll(result, "--", "-")
	}

	// Trim hyphens from start and end
	result = strings.Trim(result, "-")

	// Limit length
	if len(result) > 50 {
		result = result[:50]
	}

	return result
}

// generatePassword creates a random meeting password
func (s *JitsiService) generatePassword() string {
	return uuid.New().String()[:8]
}

// buildConfigParams creates URL parameters from config
func (s *JitsiService) buildConfigParams(config *MeetingConfig) string {
	params := url.Values{}

	if config.StartWithAudioMuted {
		params.Set("config.startWithAudioMuted", "true")
	}
	if config.StartWithVideoMuted {
		params.Set("config.startWithVideoMuted", "true")
	}
	if config.DisableDeepLinking {
		params.Set("config.disableDeepLinking", "true")
	}
	if config.RequireDisplayName {
		params.Set("config.requireDisplayName", "true")
	}
	if config.EnableLobby {
		params.Set("config.enableLobby", "true")
	}

	return params.Encode()
}

// boolToString converts bool to "true"/"false" string
func boolToString(b bool) string {
	if b {
		return "true"
	}
	return "false"
}

// GetBaseURL returns the configured base URL
func (s *JitsiService) GetBaseURL() string {
	return s.baseURL
}

// IsAuthEnabled returns whether JWT authentication is configured
func (s *JitsiService) IsAuthEnabled() bool {
	return s.appSecret != ""
}
