// Package report holds persistence entities for the manager/report
// sub-domain — user-scheduled operational reports (daily / weekly /
// monthly / custom-cron) generated by the reporter agent persona,
// viewable in-app and delivered through notification channels.
//
// Two entities (HLD-014):
//   - ReportSchedule: the user's config row (period, scope, channels,
//     persona). A cron evaluator fires it by NextFireAt.
//   - Report: one generated artifact. Mirrors alert.InvestigationReport
//     conventions — char(36) UUID id, a status machine, JSON content,
//     worker backfill, in-app drill-down.
//
// MySQL conventions inherited from the alert/device models:
//   - config rows use uint64 autoIncrement; artifact rows use char(36)
//     UUID.
//   - TEXT / longtext columns are NOT NULL (MySQL forbids DEFAULT on
//     TEXT, Error 1101); the biz layer always supplies a value (empty
//     string is the canonical "none yet").
//   - no org_id column — private-MVP single tenant; ownership is
//     created_by.
package report

import (
	"time"

	"gorm.io/gorm"
)

// Kind enumerates the schedule cadence presets. daily/weekly/monthly
// drive a quick UI picker that generates the matching CronSpec; custom
// lets the user supply a raw cron expression directly. The evaluator
// always fires off CronSpec — Kind is for UI + period derivation only.
const (
	KindDaily   = "daily"
	KindWeekly  = "weekly"
	KindMonthly = "monthly"
	KindCustom  = "custom"
)

// Report.Status state machine.
const (
	StatusPending    = "pending"    // row inserted, worker not started
	StatusGenerating = "generating" // reporter worker running
	StatusReady      = "ready"      // content written, deliverable
	StatusFailed     = "failed"     // worker crashed / LLM error / timeout
)

// DefaultReporterPersona is the agent persona that turns ReportFacts
// into ContentJSON. Overridable per-schedule via AgentPersona.
const DefaultReporterPersona = "reporter"

// ReportSchedule is one user schedule: it defines a report's cadence,
// data scope, delivery channels, and the agent persona that writes it.
// The cron evaluator selects rows WHERE enabled AND next_fire_at <= now
// and fires each.
type ReportSchedule struct {
	ID        uint64 `gorm:"primaryKey;autoIncrement;column:id"`
	CreatedBy uint64 `gorm:"column:created_by;not null;index:idx_rsched_owner"`

	Name        string `gorm:"column:name;size:128;not null"`
	Description string `gorm:"column:description;size:255;not null;default:''"`

	// Kind is the UI preset + period-derivation hint; CronSpec is the
	// real trigger source. daily/weekly/monthly are generated by the
	// front-end into a matching cron; custom is user-supplied.
	Kind     string `gorm:"column:kind;size:16;not null"`
	CronSpec string `gorm:"column:cron_spec;size:64;not null"`
	Timezone string `gorm:"column:timezone;size:64;not null;default:'UTC'"`

	// ScopeJSON is the data filter (JSON so adding fields doesn't move
	// schema). v1 fields: {fleet_tags:[], edge_ids:[], severity_min:""}.
	// Empty object = full coverage.
	ScopeJSON string `gorm:"column:scope_json;type:text;not null"`

	// Delivery
	ChannelIDsJSON string `gorm:"column:channel_ids_json;type:text;not null"` // [12,7] → notification_channels.id
	InAppVisible   bool   `gorm:"column:in_app_visible;not null;default:true"`

	// Agent config
	AgentPersona   string  `gorm:"column:agent_persona;size:64;not null;default:'reporter'"`
	PromptOverride *string `gorm:"column:prompt_override;type:text"` // NULL = use persona default

	// Scheduling state
	Enabled      bool       `gorm:"column:enabled;not null;default:true;index:idx_rsched_enabled_next,priority:1"`
	NextFireAt   *time.Time `gorm:"column:next_fire_at;index:idx_rsched_enabled_next,priority:2"`
	LastFireAt   *time.Time `gorm:"column:last_fire_at"`
	LastReportID *string    `gorm:"column:last_report_id;size:36"`

	CreatedAt time.Time      `gorm:"column:created_at;autoCreateTime"`
	UpdatedAt time.Time      `gorm:"column:updated_at;autoUpdateTime"`
	DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index"`
}

func (ReportSchedule) TableName() string { return "report_schedules" }

// Report is one generated report artifact. Mirrors InvestigationReport:
// status machine + JSON content + worker backfill + in-app drill-down.
type Report struct {
	ID string `gorm:"primaryKey;type:char(36);column:id"`

	// ScheduleID: set for cron-fired reports; NULL for manual "generate
	// now". (schedule_id, period_start) UNIQUE prevents duplicate
	// generation for the same schedule + window.
	ScheduleID *uint64 `gorm:"column:schedule_id;uniqueIndex:uniq_report_sched_period,priority:1"`
	CreatedBy  uint64  `gorm:"column:created_by;not null"` // schedule owner or manual trigger

	Title       string    `gorm:"column:title;size:255;not null"` // "周报 · 2026 W23 (6/2–6/8)"
	Kind        string    `gorm:"column:kind;size:16;not null"`
	PeriodStart time.Time `gorm:"column:period_start;not null;uniqueIndex:uniq_report_sched_period,priority:2;index:idx_report_period"`
	PeriodEnd   time.Time `gorm:"column:period_end;not null"`
	Timezone    string    `gorm:"column:timezone;size:64;not null"`

	// Locale is the output language for the report's prose (narrative +
	// advice) and the markdown section titles. For a manual generate it's
	// the requester's Accept-Language (en/zh); for a scheduled fire it's
	// "" → the generator falls back to ONGRID_DEFAULT_LOCALE. See
	// feedback_ai_output_locale.
	Locale string `gorm:"column:locale;size:8;not null;default:''"`

	// ScopeJSON snapshots the data filter this report was generated with
	// (copied from the schedule, or supplied directly for a manual
	// generate). Kept on the artifact so a report stays reproducible /
	// interpretable even after its schedule's scope is edited or the
	// schedule is deleted.
	ScopeJSON string `gorm:"column:scope_json;type:text;not null"`

	// status: pending → generating → ready / failed
	Status   string `gorm:"column:status;size:16;not null;default:'pending';index:idx_report_status_created,priority:1"`
	ErrorMsg string `gorm:"column:error_msg;type:text;not null"`

	// Content: JSON is source of truth (structured card render); MD is the
	// export / IM / search fallback.
	ContentJSON string `gorm:"column:content_json;type:longtext;not null"`
	ContentMD   string `gorm:"column:content_md;type:longtext;not null"`
	SummaryText string `gorm:"column:summary_text;size:512;not null;default:''"` // IM preview / share blurb / list subtitle

	// Generation metadata
	GeneratedAt      *time.Time `gorm:"column:generated_at"`
	GeneratedByModel string     `gorm:"column:generated_by_model;size:64;not null;default:''"`
	PromptTokens     uint64     `gorm:"column:prompt_tokens;not null;default:0"`
	CompletionTokens uint64     `gorm:"column:completion_tokens;not null;default:0"`
	AuditSessionID   *string    `gorm:"column:audit_session_id;size:36;index"` // reporter worker transcript
	WorkerID         *string    `gorm:"column:worker_id;size:64"`              // cancel / re-attach on restart

	// Share: short token, external read-only, 30-day TTL
	ShareToken     *string    `gorm:"column:share_token;size:32;uniqueIndex:idx_report_share"`
	ShareExpiresAt *time.Time `gorm:"column:share_expires_at"`

	// Delivery results: [{channel_id, channel_type, status, sent_at, error, fallback_used}]
	DeliveryJSON string `gorm:"column:delivery_json;type:text;not null"`

	CreatedAt time.Time      `gorm:"column:created_at;autoCreateTime"`
	UpdatedAt time.Time      `gorm:"column:updated_at;autoUpdateTime"`
	DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index"`
}

func (Report) TableName() string { return "reports" }
