-- Kids AI Posture Platform V1.0 database schema
-- MySQL 8.x
-- 目标：满足审计、幂等、治理、隐私与可追溯性要求

CREATE TABLE tenants (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(128) NOT NULL,
  type VARCHAR(32) NOT NULL DEFAULT 'organization',
  status VARCHAR(32) NOT NULL DEFAULT 'active',
  retention_days INT NOT NULL DEFAULT 1095,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  CHECK (type IN ('organization','school','clinic','mall','other')),
  CHECK (status IN ('active','inactive','frozen','closed')),
  CHECK (retention_days BETWEEN 30 AND 3650)
);

CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  tenant_id BIGINT NULL,
  phone VARCHAR(32),
  phone_hash VARCHAR(128),
  wechat_openid VARCHAR(128),
  role VARCHAR(32) NOT NULL DEFAULT 'parent',
  status VARCHAR(32) NOT NULL DEFAULT 'active',
  consent_signed BOOLEAN NOT NULL DEFAULT FALSE,
  privacy_version VARCHAR(16) DEFAULT 'v1',
  last_login_at TIMESTAMP NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_users_tenant_role (tenant_id, role),
  UNIQUE KEY uk_users_phone_hash (phone_hash),
  UNIQUE KEY uk_users_wechat_openid (wechat_openid),
  CHECK (role IN ('parent','coach','org_admin','platform_admin')),
  CHECK (status IN ('active','inactive','blocked'))
);

CREATE TABLE users_passwords (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  password_hash VARCHAR(256) NOT NULL,
  salt VARCHAR(64) NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_user_password (user_id)
);

CREATE TABLE children (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  tenant_id BIGINT NULL,
  parent_user_id BIGINT NOT NULL,
  coach_user_id BIGINT NULL,
  name VARCHAR(64) NOT NULL,
  gender VARCHAR(16) NOT NULL DEFAULT 'unknown',
  birthday DATE NOT NULL,
  height DECIMAL(5,2),
  weight DECIMAL(5,2),
  contraindications TEXT,
  status VARCHAR(32) NOT NULL DEFAULT 'active',
  deleted_at TIMESTAMP NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_children_parent (parent_user_id),
  KEY idx_children_tenant_coach (tenant_id, coach_user_id),
  KEY idx_children_status (status),
  CHECK (gender IN ('male','female','unknown')),
  CHECK (status IN ('active','inactive','archived')),
  CHECK (height IS NULL OR (height > 20 AND height < 240)),
  CHECK (weight IS NULL OR (weight > 5 AND weight < 250))
);

CREATE TABLE child_measurements (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  child_id BIGINT NOT NULL,
  height DECIMAL(5,2),
  weight DECIMAL(5,2),
  source VARCHAR(32) NOT NULL DEFAULT 'manual',
  measured_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_measurements_child_time (child_id, measured_at),
  CHECK (source IN ('manual','device','coach','report')), 
  CHECK (height IS NULL OR (height > 20 AND height < 240)),
  CHECK (weight IS NULL OR (weight > 5 AND weight < 250))
);

CREATE TABLE videos (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  child_id BIGINT NOT NULL,
  uploaded_by BIGINT NOT NULL,
  scene VARCHAR(64) NOT NULL,
  object_key VARCHAR(512) NOT NULL,
  duration_seconds INT,
  width INT,
  height INT,
  size_bytes BIGINT,
  codec VARCHAR(32),
  quality_result JSON,
  status VARCHAR(32) NOT NULL DEFAULT 'uploaded',
  archive_until_date DATE NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_videos_child_scene (child_id, scene),
  KEY idx_videos_status (status),
  UNIQUE KEY uk_videos_object_key (object_key),
  CHECK (scene IN ('front_posture','side_posture','squat','balance','gait','custom')),
  CHECK (status IN ('uploaded','transcoding','failed','ready','archived')),
  CHECK (duration_seconds IS NULL OR duration_seconds BETWEEN 1 AND 1800),
  CHECK (size_bytes IS NULL OR size_bytes BETWEEN 1 AND 10737418240)
);

CREATE TABLE analysis_tasks (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  child_id BIGINT NOT NULL,
  video_id BIGINT NOT NULL,
  task_type VARCHAR(64) NOT NULL,
  status VARCHAR(32) NOT NULL DEFAULT 'CREATED',
  progress INT NOT NULL DEFAULT 0,
  model_version VARCHAR(64),
  error_code VARCHAR(64),
  error_message VARCHAR(512),
  result JSON,
  idempotency_key VARCHAR(128),
  retry_count INT NOT NULL DEFAULT 0,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  started_at TIMESTAMP NULL,
  finished_at TIMESTAMP NULL,
  UNIQUE KEY uk_analysis_idempotency (child_id, video_id, task_type, idempotency_key),
  KEY idx_analysis_child_status (child_id, status),
  KEY idx_analysis_video (video_id),
  KEY idx_analysis_created (created_at),
  CHECK (status IN ('CREATED','QUEUED','PROCESSING','SUCCEEDED','FAILED','CANCELLED')),
  CHECK (task_type IN ('posture_screening','movement_scoring','reassessment')),
  CHECK (progress BETWEEN 0 AND 100),
  CHECK (retry_count BETWEEN 0 AND 20)
);

CREATE TABLE reports (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  child_id BIGINT NOT NULL,
  task_id BIGINT NOT NULL,
  report_type VARCHAR(64) NOT NULL DEFAULT 'posture_screening',
  risk_level VARCHAR(32) NOT NULL,
  summary TEXT NOT NULL,
  metrics JSON NOT NULL,
  recommendations JSON NOT NULL,
  disclaimer TEXT NOT NULL,
  status VARCHAR(32) NOT NULL DEFAULT 'draft',
  reviewed_by BIGINT NULL,
  reviewed_at TIMESTAMP NULL,
  published_at TIMESTAMP NULL,
  expires_at DATE NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_reports_task (task_id),
  KEY idx_reports_child_status (child_id, status),
  CHECK (risk_level IN ('low','medium','high','review_required')),
  CHECK (status IN ('draft','published','retracted','archived'))
);

CREATE TABLE exercises (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(128) NOT NULL,
  category VARCHAR(64) NOT NULL,
  target_issue VARCHAR(128),
  difficulty VARCHAR(32) NOT NULL DEFAULT 'basic',
  duration_seconds INT NOT NULL DEFAULT 60,
  media_url VARCHAR(512),
  thumbnail_url VARCHAR(512),
  rules JSON,
  status VARCHAR(32) NOT NULL DEFAULT 'active',
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_exercises_category (category, status),
  CHECK (difficulty IN ('basic','intermediate','advanced')),
  CHECK (status IN ('active','offline','deprecated')),
  CHECK (duration_seconds BETWEEN 20 AND 1800)
);

CREATE TABLE training_plans (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  child_id BIGINT NOT NULL,
  report_id BIGINT NULL,
  coach_user_id BIGINT NULL,
  goal VARCHAR(256) NOT NULL,
  cycle_days INT NOT NULL DEFAULT 28,
  status VARCHAR(32) NOT NULL DEFAULT 'draft',
  plan_detail JSON NOT NULL,
  started_at TIMESTAMP NULL,
  ended_at TIMESTAMP NULL,
  deleted_at TIMESTAMP NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_training_plans_child_status (child_id, status),
  CHECK (cycle_days BETWEEN 7 AND 180),
  CHECK (status IN ('draft','active','completed','paused','cancelled'))
);

CREATE TABLE training_records (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  plan_id BIGINT NOT NULL,
  child_id BIGINT NOT NULL,
  exercise_id BIGINT NOT NULL,
  completed BOOLEAN NOT NULL DEFAULT FALSE,
  score INT,
  duration_seconds INT,
  feedback JSON,
  note VARCHAR(512),
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_training_records_plan (plan_id),
  KEY idx_training_records_child_time (child_id, created_at),
  CHECK (score IS NULL OR score BETWEEN 0 AND 100),
  CHECK (duration_seconds IS NULL OR duration_seconds BETWEEN 1 AND 7200)
);

CREATE TABLE products (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  tenant_id BIGINT NULL,
  name VARCHAR(128) NOT NULL,
  product_type VARCHAR(32) NOT NULL,
  price_cents INT NOT NULL,
  duration_days INT NULL,
  benefits JSON NOT NULL,
  limit_child INTEGER NOT NULL DEFAULT 1,
  status VARCHAR(32) NOT NULL DEFAULT 'active',
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CHECK (product_type IN ('video_pack','training_plan','membership','assessment_bundle','course_package')),
  CHECK (status IN ('active','inactive','discontinued')),
  CHECK (price_cents >= 0),
  CHECK (duration_days IS NULL OR duration_days BETWEEN 1 AND 3650)
);

CREATE TABLE orders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  child_id BIGINT NULL,
  product_id BIGINT NOT NULL,
  amount_cents INT NOT NULL,
  pay_status VARCHAR(32) NOT NULL DEFAULT 'pending',
  payment_channel VARCHAR(32),
  paid_at TIMESTAMP NULL,
  canceled_at TIMESTAMP NULL,
  refund_status VARCHAR(32) DEFAULT 'none',
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_orders_user_status (user_id, pay_status),
  KEY idx_orders_child (child_id),
  CHECK (pay_status IN ('pending','paid','failed','closed','refunding','refunded')),
  CHECK (refund_status IN ('none','requested','processing','completed','rejected')),
  CHECK (amount_cents >= 0)
);

CREATE TABLE operation_leads (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  tenant_id BIGINT NOT NULL,
  child_id BIGINT NULL,
  source VARCHAR(64) NOT NULL,
  stage VARCHAR(32) NOT NULL DEFAULT 'new',
  owner_user_id BIGINT NULL,
  score INT DEFAULT 0,
  priority TINYINT DEFAULT 3,
  next_follow_at TIMESTAMP NULL,
  note TEXT,
  touched_at TIMESTAMP NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_leads_tenant_stage (tenant_id, stage),
  KEY idx_leads_owner_next (owner_user_id, next_follow_at),
  CHECK (source IN ('wechat','h5','miniapp','partner','referral','offline_campaign')),
  CHECK (stage IN ('new','contacted','qualified','closed','lost','churned')),
  CHECK (priority BETWEEN 1 AND 5),
  CHECK (score BETWEEN 0 AND 100)
);

CREATE TABLE audit_logs (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  tenant_id BIGINT NULL,
  actor_user_id BIGINT NOT NULL,
  action VARCHAR(128) NOT NULL,
  resource_type VARCHAR(64) NOT NULL,
  resource_id BIGINT NULL,
  request_id VARCHAR(64),
  ip VARCHAR(64),
  user_agent VARCHAR(256),
  payload JSON,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_audit_actor_time (actor_user_id, created_at),
  KEY idx_audit_resource (resource_type, resource_id)
);

CREATE TABLE data_retention_rules (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  tenant_id BIGINT NULL,
  resource_type VARCHAR(64) NOT NULL,
  archive_after_days INT NOT NULL,
  delete_after_days INT NOT NULL,
  legal_basis VARCHAR(128),
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_retention (tenant_id, resource_type),
  CHECK (delete_after_days >= archive_after_days),
  CHECK (archive_after_days >= 30)
);

CREATE TABLE api_idempotency_keys (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  scope VARCHAR(32) NOT NULL,
  idempotency_key VARCHAR(128) NOT NULL,
  request_path VARCHAR(255) NOT NULL,
  request_hash CHAR(64) NOT NULL,
  response_status INT,
  response_body JSON,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_idempotency_scope_key (scope, idempotency_key),
  KEY idx_idempotency_created (created_at)
);

-- 运行时可选索引补充说明（按需执行）
-- CREATE INDEX idx_analysis_status_updated ON analysis_tasks(status, updated_at);
-- CREATE INDEX idx_reports_risk_level ON reports(risk_level, created_at);
-- CREATE INDEX idx_orders_pay_status_updated ON orders(pay_status, updated_at);
