alter table public.content_assets
  add column if not exists asset_type text not null default 'document',
  add column if not exists storage_provider text not null default 'external_url',
  add column if not exists bucket text,
  add column if not exists object_key text,
  add column if not exists mime_type text,
  add column if not exists file_size_bytes bigint,
  add column if not exists checksum_sha256 text,
  add column if not exists visibility text not null default 'tenant',
  add column if not exists region_id uuid references public.regions(id) on delete set null,
  add column if not exists subject_id uuid references public.subjects(id) on delete set null,
  add column if not exists category_id uuid references public.categories(id) on delete set null,
  add column if not exists node_id uuid references public.module_nodes(id) on delete set null,
  add column if not exists preview_url text,
  add column if not exists status text not null default 'active',
  add column if not exists sort_order integer not null default 0,
  add column if not exists access_rules jsonb not null default '{}'::jsonb,
  add column if not exists created_by uuid references public.platform_users(id) on delete set null,
  add column if not exists updated_by uuid references public.platform_users(id) on delete set null,
  add column if not exists source text not null default 'manual',
  add column if not exists download_count integer not null default 0;

update public.content_assets
set visibility = case when is_public then 'public' else visibility end,
    storage_provider = case when cdn_url is not null and cdn_url <> '' then 'external_url' else storage_provider end
where visibility = 'tenant' or storage_provider = 'external_url';

do $$
begin
  if not exists (select 1 from pg_constraint where conname = 'content_assets_asset_type_check') then
    alter table public.content_assets
      add constraint content_assets_asset_type_check
      check (asset_type in ('pdf', 'video', 'image', 'audio', 'document', 'package', 'link', 'other'));
  end if;

  if not exists (select 1 from pg_constraint where conname = 'content_assets_storage_provider_check') then
    alter table public.content_assets
      add constraint content_assets_storage_provider_check
      check (storage_provider in ('external_url', 'supabase_storage', 'aliyun_oss', 'tencent_cos', 'qiniu_kodo', 'local_dev'));
  end if;

  if not exists (select 1 from pg_constraint where conname = 'content_assets_visibility_check') then
    alter table public.content_assets
      add constraint content_assets_visibility_check
      check (visibility in ('public', 'tenant', 'members', 'svip', 'private'));
  end if;

  if not exists (select 1 from pg_constraint where conname = 'content_assets_status_check') then
    alter table public.content_assets
      add constraint content_assets_status_check
      check (status in ('draft', 'active', 'archived'));
  end if;

  if not exists (select 1 from pg_constraint where conname = 'content_assets_file_size_check') then
    alter table public.content_assets
      add constraint content_assets_file_size_check
      check (file_size_bytes is null or file_size_bytes >= 0);
  end if;

  if not exists (select 1 from pg_constraint where conname = 'content_assets_download_count_check') then
    alter table public.content_assets
      add constraint content_assets_download_count_check
      check (download_count >= 0);
  end if;
end $$;

create table if not exists public.content_import_jobs (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  created_by uuid references public.platform_users(id) on delete set null,
  import_type text not null check (import_type in ('questions', 'vocabulary', 'handbook', 'scoreline', 'assets', 'videos')),
  source_format text not null default 'json' check (source_format in ('json', 'excel', 'csv', 'pocketbase', 'api')),
  status text not null default 'preview' check (status in ('preview', 'pending', 'importing', 'completed', 'completed_with_errors', 'failed', 'rejected')),
  source_name text,
  source_hash text,
  target_region_id uuid references public.regions(id) on delete set null,
  target_subject_id uuid references public.subjects(id) on delete set null,
  target_category_id uuid references public.categories(id) on delete set null,
  target_node_id uuid references public.module_nodes(id) on delete set null,
  target_question_bank_id uuid references public.question_banks(id) on delete set null,
  dry_run boolean not null default true,
  total_count integer not null default 0,
  valid_count integer not null default 0,
  error_count integer not null default 0,
  warning_count integer not null default 0,
  inserted_count integer not null default 0,
  updated_count integer not null default 0,
  skipped_count integer not null default 0,
  summary jsonb not null default '{}'::jsonb,
  raw_payload jsonb not null default '[]'::jsonb,
  normalized_payload jsonb not null default '[]'::jsonb,
  error_message text,
  started_at timestamptz,
  finished_at timestamptz,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create table if not exists public.content_import_items (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  job_id uuid not null references public.content_import_jobs(id) on delete cascade,
  row_no integer not null,
  external_id text,
  status text not null default 'valid' check (status in ('valid', 'invalid', 'inserted', 'updated', 'skipped', 'failed')),
  target_type text,
  target_id uuid,
  source_payload jsonb not null default '{}'::jsonb,
  normalized_payload jsonb not null default '{}'::jsonb,
  content_hash text,
  issues_count integer not null default 0,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (job_id, row_no)
);

create table if not exists public.content_import_issues (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  job_id uuid not null references public.content_import_jobs(id) on delete cascade,
  item_id uuid references public.content_import_items(id) on delete cascade,
  row_no integer,
  severity text not null default 'error' check (severity in ('error', 'warning')),
  code text not null,
  field_path text,
  message text not null,
  details jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create index if not exists idx_content_assets_filters
  on public.content_assets(tenant_id, status, visibility, asset_type, region_id, subject_id, category_id, sort_order);
create index if not exists idx_content_assets_object
  on public.content_assets(tenant_id, storage_provider, bucket, object_key);
create index if not exists idx_content_assets_checksum
  on public.content_assets(tenant_id, checksum_sha256)
  where checksum_sha256 is not null;

create index if not exists idx_content_import_jobs_tenant_status
  on public.content_import_jobs(tenant_id, import_type, status, created_at desc);
create index if not exists idx_content_import_items_job_status
  on public.content_import_items(tenant_id, job_id, status, row_no);
create index if not exists idx_content_import_issues_job
  on public.content_import_issues(tenant_id, job_id, severity, row_no);

do $$
declare
  table_name text;
begin
  foreach table_name in array array[
    'content_import_jobs', 'content_import_items', 'content_import_issues'
  ]
  loop
    execute format('alter table public.%I enable row level security', table_name);
    execute format('drop policy if exists tenant_isolation on public.%I', table_name);
    execute format(
      'create policy tenant_isolation on public.%I for all using (tenant_id = app.current_tenant_id() or app.is_platform_admin()) with check (tenant_id = app.current_tenant_id() or app.is_platform_admin())',
      table_name
    );
    execute format('drop trigger if exists set_updated_at on public.%I', table_name);
    execute format('create trigger set_updated_at before update on public.%I for each row execute function app.touch_updated_at()', table_name);
  end loop;
end $$;
