BEGIN;

CREATE TABLE IF NOT EXISTS operation_groups (
  id BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL UNIQUE,
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS users (
  id BIGSERIAL PRIMARY KEY,
  group_id BIGINT REFERENCES operation_groups(id) ON DELETE RESTRICT,
  username TEXT NOT NULL UNIQUE,
  display_name TEXT NOT NULL,
  password_hash TEXT NOT NULL,
  role TEXT NOT NULL CHECK (role IN ('platform_admin', 'group_admin', 'operator')),
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
  must_change_password BOOLEAN NOT NULL DEFAULT TRUE,
  last_login_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX IF NOT EXISTS one_group_admin_per_group ON users(group_id) WHERE role = 'group_admin';

CREATE TABLE IF NOT EXISTS projects (
  id BIGSERIAL PRIMARY KEY,
  group_id BIGINT NOT NULL REFERENCES operation_groups(id) ON DELETE RESTRICT,
  name TEXT NOT NULL,
  slug TEXT NOT NULL UNIQUE,
  client_description TEXT NOT NULL DEFAULT '',
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'closed', 'archived')),
  access_password_hash TEXT NOT NULL DEFAULT '',
  customer_access_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  access_expires_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS collections (
  id BIGSERIAL PRIMARY KEY,
  project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  client_description TEXT NOT NULL DEFAULT '',
  status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'reviewing', 'completed', 'archived')),
  completed_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE (project_id, name)
);

ALTER TABLE collections ADD COLUMN IF NOT EXISTS completed_at TIMESTAMPTZ;

CREATE TABLE IF NOT EXISTS notes (
  id BIGSERIAL PRIMARY KEY,
  collection_id BIGINT NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
  external_id TEXT,
  title TEXT NOT NULL,
  description TEXT NOT NULL DEFAULT '',
  tags TEXT NOT NULL DEFAULT '[]',
  review_status TEXT NOT NULL DEFAULT 'pending' CHECK (review_status IN ('draft', 'pending', 'changes_requested', 'approved')),
  version_number INTEGER NOT NULL DEFAULT 1 CHECK (version_number > 0),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

ALTER TABLE notes ADD COLUMN IF NOT EXISTS external_id TEXT;
CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_collection_external_id ON notes(collection_id, external_id) WHERE external_id IS NOT NULL AND external_id != '';

CREATE TABLE IF NOT EXISTS images (
  id BIGSERIAL PRIMARY KEY,
  note_id BIGINT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  url TEXT NOT NULL,
  width INTEGER NOT NULL DEFAULT 0,
  height INTEGER NOT NULL DEFAULT 0,
  order_index INTEGER NOT NULL DEFAULT 0,
  storage_provider TEXT NOT NULL DEFAULT 'local' CHECK (storage_provider IN ('local', 'tencent_cos', 'external')),
  storage_key TEXT NOT NULL DEFAULT '',
  version_number INTEGER NOT NULL DEFAULT 1 CHECK (version_number > 0)
);

CREATE TABLE IF NOT EXISTS annotations (
  id BIGSERIAL PRIMARY KEY,
  image_id BIGINT NOT NULL REFERENCES images(id) ON DELETE CASCADE,
  x DOUBLE PRECISION NOT NULL CHECK (x BETWEEN 0 AND 1),
  y DOUBLE PRECISION NOT NULL CHECK (y BETWEEN 0 AND 1),
  content TEXT NOT NULL,
  author_name TEXT NOT NULL DEFAULT '客户',
  status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'resolved', 'confirmed')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS work_comments (
  id BIGSERIAL PRIMARY KEY,
  note_id BIGINT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  content TEXT NOT NULL,
  author_name TEXT NOT NULL DEFAULT '客户',
  author_role TEXT NOT NULL DEFAULT 'client' CHECK (author_role IN ('client', 'operator')),
  status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'resolved', 'confirmed')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS text_annotations (
  id BIGSERIAL PRIMARY KEY,
  note_id BIGINT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  version_number INTEGER NOT NULL CHECK (version_number > 0),
  target TEXT NOT NULL CHECK (target IN ('title', 'description')),
  content TEXT NOT NULL,
  author_name TEXT NOT NULL DEFAULT '客户',
  status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'resolved', 'confirmed')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_text_annotations_note_id ON text_annotations(note_id, version_number);

CREATE TABLE IF NOT EXISTS work_versions (
  id BIGSERIAL PRIMARY KEY,
  note_id BIGINT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  version_number INTEGER NOT NULL CHECK (version_number > 0),
  title TEXT NOT NULL,
  description TEXT NOT NULL DEFAULT '',
  tags TEXT NOT NULL DEFAULT '[]',
  review_status TEXT NOT NULL DEFAULT 'pending' CHECK (review_status IN ('draft', 'pending', 'changes_requested', 'approved')),
  created_by BIGINT REFERENCES users(id) ON DELETE SET NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE (note_id, version_number)
);

CREATE TABLE IF NOT EXISTS review_events (
  id BIGSERIAL PRIMARY KEY,
  note_id BIGINT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  version_number INTEGER NOT NULL,
  event_type TEXT NOT NULL CHECK (event_type IN ('submitted', 'approved', 'changes_requested', 'reopened')),
  from_status TEXT,
  to_status TEXT NOT NULL,
  reason TEXT NOT NULL DEFAULT '',
  actor_name TEXT NOT NULL,
  actor_role TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS sessions (
  id BIGSERIAL PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  token_hash TEXT NOT NULL UNIQUE,
  expires_at TIMESTAMPTZ NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS customer_sessions (
  id BIGSERIAL PRIMARY KEY,
  project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
  reviewer_name TEXT NOT NULL,
  token_hash TEXT NOT NULL UNIQUE,
  expires_at TIMESTAMPTZ NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS audit_logs (
  id BIGSERIAL PRIMARY KEY,
  group_id BIGINT REFERENCES operation_groups(id) ON DELETE SET NULL,
  user_id BIGINT REFERENCES users(id) ON DELETE SET NULL,
  action TEXT NOT NULL,
  entity_type TEXT NOT NULL,
  entity_id BIGINT,
  detail TEXT NOT NULL DEFAULT '{}',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS api_keys (
  id BIGSERIAL PRIMARY KEY,
  group_id BIGINT REFERENCES operation_groups(id) ON DELETE RESTRICT,
  project_id BIGINT REFERENCES projects(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  key_prefix TEXT NOT NULL UNIQUE,
  key_hash TEXT NOT NULL UNIQUE,
  scope TEXT NOT NULL CHECK (scope IN ('platform', 'project')),
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
  created_by BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
  last_used_at TIMESTAMPTZ,
  revoked_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS storage_configs (
  id BIGSERIAL PRIMARY KEY,
  provider TEXT NOT NULL DEFAULT 'tencent_cos',
  region TEXT NOT NULL,
  bucket TEXT NOT NULL,
  public_base_url TEXT NOT NULL DEFAULT '',
  cdn_domain TEXT NOT NULL DEFAULT '',
  path_prefix TEXT NOT NULL DEFAULT 'delivery-desk',
  secret_id_encrypted TEXT NOT NULL,
  secret_key_encrypted TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'archived')),
  test_status TEXT NOT NULL DEFAULT 'untested' CHECK (test_status IN ('untested', 'passed', 'failed')),
  test_message TEXT NOT NULL DEFAULT '',
  last_tested_at TIMESTAMPTZ,
  created_by BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  activated_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX IF NOT EXISTS one_active_storage_config ON storage_configs ((status)) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS collections_project_id_idx ON collections(project_id);
CREATE INDEX IF NOT EXISTS notes_collection_id_idx ON notes(collection_id);
CREATE INDEX IF NOT EXISTS images_note_version_idx ON images(note_id, version_number, order_index);
CREATE INDEX IF NOT EXISTS annotations_image_id_idx ON annotations(image_id);
CREATE INDEX IF NOT EXISTS comments_note_id_idx ON work_comments(note_id);
CREATE INDEX IF NOT EXISTS customer_sessions_project_id_idx ON customer_sessions(project_id);
CREATE INDEX IF NOT EXISTS review_events_note_version_idx ON review_events(note_id, version_number);
CREATE INDEX IF NOT EXISTS audit_logs_group_id_idx ON audit_logs(group_id);

-- COLLECTION_STATUS_REPAIR_START
UPDATE collections
SET status = 'draft', completed_at = NULL
WHERE status != 'archived'
  AND id NOT IN (SELECT collection_id FROM notes WHERE review_status != 'draft');

UPDATE collections AS c
SET status = CASE WHEN s.approved_count = s.work_count THEN 'completed' ELSE 'reviewing' END,
  completed_at = CASE WHEN s.approved_count = s.work_count THEN COALESCE(c.completed_at, NOW()) ELSE NULL END
FROM (
  SELECT collection_id,
    COUNT(*) AS work_count,
    SUM(CASE WHEN review_status = 'approved' THEN 1 ELSE 0 END) AS approved_count
  FROM notes
  WHERE review_status != 'draft'
  GROUP BY collection_id
) AS s
WHERE c.id = s.collection_id AND c.status != 'archived';
-- COLLECTION_STATUS_REPAIR_END

COMMIT;
