package models

import (
	"time"

	"github.com/google/uuid"
)

// ── Enums ────────────────────────────────────────────────────────────────────

const (
	// NodeStatus
	NodeStatusReady       = "Ready"
	NodeStatusBusy        = "Busy"
	NodeStatusOffline     = "Offline"
	NodeStatusMaintenance = "Maintenance"
	NodeStatusCrashed     = "Crashed"
	NodeStatusUpdating    = "Updating"
	NodeStatusDisabled    = "Disabled"

	// NodeKind
	NodeKindShared    = "Shared"
	NodeKindDedicated = "Dedicated"
	NodeKindSpot      = "Spot"

	// RenderStep
	StepQueued       = "Queued"
	StepPreparing    = "Preparing"
	StepTemplateCache = "TemplateCache"
	StepJsxGen       = "JsxGen"
	StepMusic        = "Music"
	StepRendering    = "Rendering"
	StepValidating   = "Validating"
	StepRepairing    = "Repairing"
	StepOptimisation = "Optimisation"
	StepVideo        = "Video"
	StepMixing       = "Mixing"
	StepFinal        = "Final"
	StepUploading    = "Uploading"
	StepDone         = "Done"
	StepFailed       = "Failed"
	StepCancelled    = "Cancelled"

	// PriceKind
	PriceKindFree      = "Free"
	PriceKindPreview   = "Preview"
	PriceKindCash      = "Cash"
	PriceKindPlan      = "Plan"
	PriceKindSnapshot  = "Snapshot"
	PriceKindReseller  = "Reseller"

	// RenderQuality
	QualityLow      = "Low"
	QualityMedium   = "Medium"
	QualityHigh     = "High"
	QualityFull     = "Full"
	QualityLossless = "Lossless"

	// FrameJobStatus
	FrameJobPending    = "Pending"
	FrameJobRendering  = "Rendering"
	FrameJobValidated  = "Validated"
	FrameJobRepairing  = "Repairing"
	FrameJobConverting = "Converting"
	FrameJobDone       = "Done"
	FrameJobFailed     = "Failed"

	// PriorityQueue
	QueueSnapshot  = "snapshot"
	QueueVIP       = "vip"
	QueuePaid      = "paid"
	QueuePreview   = "preview"
	QueueMockup    = "mockup"
	QueueVoiceover = "voiceover"

	// Export types
	ExportCreateRender   = "Render"
	ExportCreateUpload   = "Upload"
	ExportCreateSnapshot = "Snapshot"
	ExportCreateReupload = "Reupload"

	ExportFileVideo = "Video"
	ExportFileImage = "Image"
	ExportFileAudio = "Audio"
	ExportFileGIF   = "GIF"
	ExportFilePDF   = "PDF"
)

// ── Domain entities ──────────────────────────────────────────────────────────

type RenderNode struct {
	ID                  uuid.UUID  `json:"id"`
	Name                string     `json:"name"`
	Region              string     `json:"region"`
	NodeIP              string     `json:"node_ip"`
	WorkerPort          int        `json:"worker_port"`
	PublicEndpoint      *string    `json:"public_endpoint,omitempty"`
	RamGB               *int       `json:"ram_gb,omitempty"`
	CPUCores            *int       `json:"cpu_cores,omitempty"`
	GPUModel            *string    `json:"gpu_model,omitempty"`
	StorageGB           *int       `json:"storage_gb,omitempty"`
	CurrentAEVersion    string     `json:"current_ae_version"`
	AvailableAEVersions []string   `json:"available_ae_versions"`
	NodeAgentVersion    *string    `json:"node_agent_version,omitempty"`
	NodeKind            string     `json:"node_kind"`
	OwnerUserID         *uuid.UUID `json:"owner_user_id,omitempty"`
	OwnerTenantID       *uuid.UUID `json:"owner_tenant_id,omitempty"`
	Status              string     `json:"status"`
	CurrentJobID        *uuid.UUID `json:"current_job_id,omitempty"`
	CurrentFrameJobID   *uuid.UUID `json:"current_frame_job_id,omitempty"`
	JobStartedAt        *time.Time `json:"job_started_at,omitempty"`
	LastHeartbeatAt     *time.Time `json:"last_heartbeat_at,omitempty"`
	LastCPUPct          *int       `json:"last_cpu_pct,omitempty"`
	LastRAMAvailableMB  *int       `json:"last_ram_available_mb,omitempty"`
	LastDiskPct         *int       `json:"last_disk_pct,omitempty"`
	DiskTotalGB         *int       `json:"disk_total_gb,omitempty"`
	AERunning           bool       `json:"ae_running"`
	LifetimeTaskCount   int64      `json:"lifetime_task_count"`
	LifetimeCrashCount  int        `json:"lifetime_crash_count"`
	ConsecutiveFailures int        `json:"consecutive_failures"`
	Priority            int        `json:"priority"`
	IsActive            bool       `json:"is_active"`
	AcceptsNewJobs      bool       `json:"accepts_new_jobs"`
	LastMaintenanceAt   *time.Time `json:"last_maintenance_at,omitempty"`
	NextMaintenanceAt   *time.Time `json:"next_maintenance_at,omitempty"`
	MaintenanceReason   *string    `json:"maintenance_reason,omitempty"`
	CachedTemplateMD5s  []string   `json:"cached_template_md5s"`
	CacheUsedGB         int        `json:"cache_used_gb"`
	CreatedAt           time.Time  `json:"created_at"`
	UpdatedAt           time.Time  `json:"updated_at"`
}

type RenderJob struct {
	ID                  uuid.UUID  `json:"id"`
	TenantID            uuid.UUID  `json:"tenant_id"`
	UserID              uuid.UUID  `json:"user_id"`
	SavedProjectID      uuid.UUID  `json:"saved_project_id"`
	OriginalProjectID   uuid.UUID  `json:"original_project_id"`
	ProjectName         *string    `json:"project_name,omitempty"`
	Title               *string    `json:"title,omitempty"`
	Name                *string    `json:"name,omitempty"`
	ExternalJobID       *string    `json:"external_job_id,omitempty"`
	PriorityQueue       string     `json:"priority_queue"`
	PriorityScore       int        `json:"priority_score"`
	Step                string     `json:"step"`
	RenderProgress      int        `json:"render_progress"`
	ConvertProgress     int        `json:"convert_progress"`
	ImagePreviewB64     *string    `json:"image_preview_b64,omitempty"`
	PriceType           string     `json:"price_type"`
	PaidPriceMinor      int64      `json:"paid_price_minor"`
	DiscountCode        *string    `json:"discount_code,omitempty"`
	SupportFlatrender   bool       `json:"support_flatrender"`
	Mode                string     `json:"mode"`
	Quality             string     `json:"quality"`
	Resolution          string     `json:"resolution"`
	RHeight             int        `json:"r_height"`
	FrameRate           int        `json:"frame_rate"`
	Is60FPS             bool       `json:"is_60_fps"`
	DurationSec         float64    `json:"duration_sec"`
	ExportDurationSec   *float64   `json:"export_duration_sec,omitempty"`
	HasMusic            bool       `json:"has_music"`
	HasSFX              bool       `json:"has_sfx"`
	HasVoiceover        bool       `json:"has_voiceover"`
	MusicVolume         *float64   `json:"music_volume,omitempty"`
	SFXVolume           *float64   `json:"sfx_volume,omitempty"`
	VoiceoverVolume     *float64   `json:"voiceover_volume,omitempty"`
	RenderNodeCount     int        `json:"render_node_count"`
	CurrentActiveNodes  int        `json:"current_active_nodes"`
	Region              *string    `json:"region,omitempty"`
	TellMeWhenDone      bool       `json:"tell_me_when_done"`
	RetryCount          int        `json:"retry_count"`
	MaxRetries          int        `json:"max_retries"`
	RepairAttempts      int        `json:"repair_attempts"`
	FailedMessage       *string    `json:"failed_message,omitempty"`
	FailedAtStep        *string    `json:"failed_at_step,omitempty"`
	ExportID            *uuid.UUID `json:"export_id,omitempty"`
	TaskStartDate       time.Time  `json:"task_start_date"`
	QueuedAt            time.Time  `json:"queued_at"`
	StartedAt           *time.Time `json:"started_at,omitempty"`
	CompletedAt         *time.Time `json:"completed_at,omitempty"`
	CreatedAt           time.Time  `json:"created_at"`
	UpdatedAt           time.Time  `json:"updated_at"`
	// Admin-list enrichment (set by the handler via a cross-schema lookup, not scanned).
	UserName  string `json:"user_name,omitempty"`
	UserEmail string `json:"user_email,omitempty"`
}

type FrameJob struct {
	ID                 uuid.UUID  `json:"id"`
	RenderJobID        uuid.UUID  `json:"render_job_id"`
	NodeID             uuid.UUID  `json:"node_id"`
	StartFrame         int        `json:"start_frame"`
	EndFrame           int        `json:"end_frame"`
	CollectFrameCount  int        `json:"collect_frame_count"`
	OrderValue         int        `json:"order_value"`
	FolderName         string     `json:"folder_name"`
	ConvertURL         *string    `json:"convert_url,omitempty"`
	Status             string     `json:"status"`
	FramesRendered     int        `json:"frames_rendered"`
	FramesValidated    int        `json:"frames_validated"`
	Attempt            int        `json:"attempt"`
	LastError          *string    `json:"last_error,omitempty"`
	OutputMP4URL       *string    `json:"output_mp4_url,omitempty"`
	AssignedAt         time.Time  `json:"assigned_at"`
	StartedAt          *time.Time `json:"started_at,omitempty"`
	LastProgressAt     *time.Time `json:"last_progress_at,omitempty"`
	CompletedAt        *time.Time `json:"completed_at,omitempty"`
	CreatedAt          time.Time  `json:"created_at"`
	UpdatedAt          time.Time  `json:"updated_at"`
}

type Snapshot struct {
	ID             uuid.UUID  `json:"id"`
	TenantID       uuid.UUID  `json:"tenant_id"`
	UserID         uuid.UUID  `json:"user_id"`
	SavedProjectID uuid.UUID  `json:"saved_project_id"`
	SceneKey       string     `json:"scene_key"`
	FrameNumber    int        `json:"frame_number"`
	InputsHash     string     `json:"inputs_hash"`
	Status         string     `json:"status"`
	RenderNodeID   *uuid.UUID `json:"render_node_id,omitempty"`
	ImageURL       *string    `json:"image_url,omitempty"`
	ThumbnailURL   *string    `json:"thumbnail_url,omitempty"`
	Width          *int       `json:"width,omitempty"`
	Height         *int       `json:"height,omitempty"`
	SizeBytes      *int64     `json:"size_bytes,omitempty"`
	RequestedAt    time.Time  `json:"requested_at"`
	CompletedAt    *time.Time `json:"completed_at,omitempty"`
	DurationMS     *int       `json:"duration_ms,omitempty"`
	ExpiresAt      time.Time  `json:"expires_at"`
	ErrorMessage   *string    `json:"error_message,omitempty"`
	CreatedAt      time.Time  `json:"created_at"`
}

type Export struct {
	ID             uuid.UUID  `json:"id"`
	TenantID       uuid.UUID  `json:"tenant_id"`
	UserID         uuid.UUID  `json:"user_id"`
	SavedProjectID uuid.UUID  `json:"saved_project_id"`
	ProjectID      uuid.UUID  `json:"project_id"`
	RenderJobID    *uuid.UUID `json:"render_job_id,omitempty"`
	Image          *string    `json:"image,omitempty"`
	Path           string     `json:"path"`
	FileExtension  string     `json:"file_extension"`
	FileType       string     `json:"file_type"`
	RenderQuality  string     `json:"render_quality"`
	CreateType     string     `json:"create_type"`
	SizeBytes      int64      `json:"size_bytes"`
	DurationSec    *float64   `json:"duration_sec,omitempty"`
	Width          *int       `json:"width,omitempty"`
	Height         *int       `json:"height,omitempty"`
	ProduceDate    time.Time  `json:"produce_date"`
	AutoDeleteDate time.Time  `json:"auto_delete_date"`
	DeleteNotified bool       `json:"delete_notified"`
	CreatedAt      time.Time  `json:"created_at"`
	DeletedAt      *time.Time `json:"deleted_at,omitempty"`
}

type ExportFile struct {
	ID        uuid.UUID `json:"id"`
	ExportID  uuid.UUID `json:"export_id"`
	UserID    uuid.UUID `json:"user_id"`
	Name      *string   `json:"name,omitempty"`
	Thumbnail *string   `json:"thumbnail,omitempty"`
	Path      string    `json:"path"`
	SizeBytes int64     `json:"size_bytes"`
	FileType  string    `json:"file_type"`
	Width     *int      `json:"width,omitempty"`
	Height    *int      `json:"height,omitempty"`
	Sort      int       `json:"sort"`
	CreatedAt time.Time `json:"created_at"`
}

type NodeHealthLog struct {
	ID             int64      `json:"id"`
	NodeID         uuid.UUID  `json:"node_id"`
	RecordedAt     time.Time  `json:"recorded_at"`
	Status         string     `json:"status"`
	CPUPct         *int       `json:"cpu_pct,omitempty"`
	RAMAvailableMB *int       `json:"ram_available_mb,omitempty"`
	AERunning      *bool      `json:"ae_running,omitempty"`
	CurrentJobID   *uuid.UUID `json:"current_job_id,omitempty"`
	CurrentFrame   *int       `json:"current_frame,omitempty"`
	CacheUsedGB    *int       `json:"cache_used_gb,omitempty"`
}

type NodeCrash struct {
	ID            uuid.UUID  `json:"id"`
	NodeID        uuid.UUID  `json:"node_id"`
	RenderJobID   *uuid.UUID `json:"render_job_id,omitempty"`
	FrameJobID    *uuid.UUID `json:"frame_job_id,omitempty"`
	CrashedAt     time.Time  `json:"crashed_at"`
	LastKnownFrame *int      `json:"last_known_frame,omitempty"`
	CrashSignal   *string    `json:"crash_signal,omitempty"`
	ErrorLog      *string    `json:"error_log,omitempty"`
	LogFileURL    *string    `json:"log_file_url,omitempty"`
	AutoRecovered bool       `json:"auto_recovered"`
	RecoveryAction *string   `json:"recovery_action,omitempty"`
	RecoveredAt   *time.Time `json:"recovered_at,omitempty"`
	CreatedAt     time.Time  `json:"created_at"`
}

type NodeUpdate struct {
	ID                    uuid.UUID   `json:"id"`
	UpdateFileName        string      `json:"update_file_name"`
	UpdateNumber          int         `json:"update_number"`
	Description           *string     `json:"description,omitempty"`
	TargetAEVersion       *string     `json:"target_ae_version,omitempty"`
	InUpdateQueue         bool        `json:"in_update_queue"`
	RolledOutToNodeIDs    []uuid.UUID `json:"rolled_out_to_node_ids"`
	LastUpdateQueueDate   *time.Time  `json:"last_update_queue_date,omitempty"`
	CreateDate            time.Time   `json:"create_date"`
	CreatedAt             time.Time   `json:"created_at"`
}

// ── Request / Response types ─────────────────────────────────────────────────

type PagedResponse[T any] struct {
	Data []T            `json:"data"`
	Meta PaginationMeta `json:"meta"`
}

type PaginationMeta struct {
	Page     int   `json:"page"`
	PageSize int   `json:"page_size"`
	Total    int64 `json:"total"`
	HasMore  bool  `json:"has_more"`
}

type RenderJobCreateRequest struct {
	SavedProjectID    uuid.UUID `json:"saved_project_id" binding:"required"`
	Quality           string    `json:"quality"           binding:"required"`
	Resolution        string    `json:"resolution"        binding:"required"`
	FrameRate         *int      `json:"frame_rate"`
	Is60FPS           *bool     `json:"is_60_fps"`
	PriceType         *string   `json:"price_type"`
	DiscountCode      *string   `json:"discount_code"`
	SupportFlatrender *bool     `json:"support_flatrender"`
	TellMeWhenDone    *bool     `json:"tell_me_when_done"`
	PreferredRegion   *string   `json:"preferred_region"`
}

type SnapshotCreateRequest struct {
	SavedProjectID uuid.UUID `json:"saved_project_id" binding:"required"`
	SceneKey       string    `json:"scene_key"        binding:"required"`
	FrameNumber    int       `json:"frame_number"     binding:"min=0"`
}

type NodeCreateRequest struct {
	Name             string     `json:"name"               binding:"required"`
	Region           string     `json:"region"             binding:"required"`
	NodeIP           string     `json:"node_ip"            binding:"required"`
	WorkerPort       int        `json:"worker_port"        binding:"required"`
	CurrentAEVersion string     `json:"current_ae_version" binding:"required"`
	RamGB            *int       `json:"ram_gb"`
	CPUCores         *int       `json:"cpu_cores"`
	NodeKind         *string    `json:"node_kind"`
	OwnerUserID      *uuid.UUID `json:"owner_user_id"`
	Priority         *int       `json:"priority"`
}

type NodePatchRequest struct {
	Priority           *int       `json:"priority"`
	IsActive           *bool      `json:"is_active"`
	AcceptsNewJobs     *bool      `json:"accepts_new_jobs"`
	NodeKind           *string    `json:"node_kind"`
	OwnerUserID        *uuid.UUID `json:"owner_user_id"`
	NextMaintenanceAt  *time.Time `json:"next_maintenance_at"`
	MaintenanceReason  *string    `json:"maintenance_reason"`
}

type NodeHeartbeatRequest struct {
	NodeID         uuid.UUID  `json:"node_id"`
	Status         string     `json:"status"`
	CPUPct         *int       `json:"cpu_pct"`
	RAMAvailableMB *int       `json:"ram_available_mb"`
	DiskUsedPct    *int       `json:"disk_used_pct"`
	DiskTotalGB    *int       `json:"disk_total_gb"`
	AERunning      *bool      `json:"ae_running"`
	CurrentJobID   *uuid.UUID `json:"current_job_id"`
	CurrentFrame   *int       `json:"current_frame"`
	CacheUsedGB    *int       `json:"cache_used_gb"`
}

type NodeOnlineRequest struct {
	NodeAgentVersion    string   `json:"node_agent_version"`
	CurrentAEVersion    string   `json:"current_ae_version"`
	AvailableAEVersions []string `json:"available_ae_versions"`
	RamGB               *int     `json:"ram_gb"`
	CPUCores            *int     `json:"cpu_cores"`
	CacheUsedGB         *int     `json:"cache_used_gb"`
	CachedTemplateMD5s  []string `json:"cached_template_md5s"`
}

type FrameProgressRequest struct {
	FrameJobID  uuid.UUID  `json:"frame_job_id"  binding:"required"`
	FrameNumber int        `json:"frame_number"`
	FileSizeBytes *int64   `json:"file_size_bytes"`
	CompletedAt *time.Time `json:"completed_at"`
}

type CrashReportRequest struct {
	NodeID         uuid.UUID  `json:"node_id"  binding:"required"`
	FrameJobID     *uuid.UUID `json:"frame_job_id"`
	LastKnownFrame *int       `json:"last_known_frame"`
	CrashSignal    *string    `json:"crash_signal"`
	AEVersion      *string    `json:"ae_version"`
	ErrorLogTail   *string    `json:"error_log_tail"`
	LogFileURL     *string    `json:"log_file_url"`
}

type ClaimJobRequest struct {
	NodeID  uuid.UUID `json:"node_id"  binding:"required"`
	Region  string    `json:"region"`
}

type ClaimedJob struct {
	JobID          uuid.UUID `json:"job_id"`
	SavedProjectID uuid.UUID `json:"saved_project_id"`
	Quality        string    `json:"quality"`
	Resolution     string    `json:"resolution"`
	FrameRate      int       `json:"frame_rate"`
	HasMusic       bool      `json:"has_music"`
	HasVoiceover   bool      `json:"has_voiceover"`
	// AEPDownloadURL is a presigned MinIO GET URL for the .aep project file
	// (or .zip bundle). Valid for 2 hours. Empty when the template is not yet uploaded.
	AEPDownloadURL string `json:"aep_download_url,omitempty"`
	// IsBundle is true when AEPDownloadURL points to a .zip bundle (the .aep plus
	// footage/fonts) that the node agent must extract before rendering.
	IsBundle bool `json:"is_bundle,omitempty"`
	// BundleMD5 is the stored object's ETag/MD5 — the node uses it as a cache key so
	// repeated renders of the same template download + extract the bundle only once.
	BundleMD5 string `json:"bundle_md5,omitempty"`
	// CompName is the After Effects composition to render (-comp). From the
	// template's render_aep_comp (e.g. "frfinal"). Empty → node falls back to the
	// project's render queue.
	CompName string `json:"comp_name,omitempty"`
	// Bindings are the user's edited input values to write into the AE project before
	// rendering (the render binder). Key = AE layer/footage name (frl_c{n}{t|m}{i}).
	Bindings []RenderBinding `json:"bindings,omitempty"`
}

// RenderBinding is one input value the node writes into the AE project before render:
// a Text layer's source text, or a Media footage replacement.
type RenderBinding struct {
	Key   string `json:"key"`   // AE layer/footage name, e.g. frl_c1t1 / frl_c1m1
	Type  string `json:"type"`  // content element type (Text, Media, …)
	Value string `json:"value"` // text content, or a media URL
}

// OutputUploadURLResponse is returned by POST .../output-upload-url.
type OutputUploadURLResponse struct {
	ExportID  uuid.UUID `json:"export_id"`
	UploadURL string    `json:"upload_url"`
	ObjectKey string    `json:"object_key"`
	ExpiresAt time.Time `json:"expires_at"`
}

type CacheUpdateRequest struct {
	Action        string     `json:"action"         binding:"required"`
	ProjectID     *uuid.UUID `json:"project_id"`
	AEPFileMD5    string     `json:"aep_file_md5"   binding:"required"`
	FileSizeBytes *int64     `json:"file_size_bytes"`
	CacheUsedGB   *int       `json:"cache_used_gb"`
	ErrorMessage  *string    `json:"error_message"`
}

type ReplicaReadyRequest struct {
	NodeID      uuid.UUID `json:"node_id"      binding:"required"`
	ReplicaPath string    `json:"replica_path" binding:"required"`
	ReplicaMD5  *string   `json:"replica_md5"`
}

type APIError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

// Claims carries JWT payload
type Claims struct {
	UserID   uuid.UUID `json:"sub"`
	TenantID uuid.UUID `json:"tenant_id"`
	IsAdmin  bool      `json:"is_admin"`
	Role     string    `json:"role"`
}
