--[ Database Schema Version ]--
-- Version: 1.0.0
-- Last Updated: 2024-02-24
-- Description: Core schema setup for ClassConcepts with neoFS filesystem integration
-- Dependencies: auth.users (Supabase Auth)

--[ Validation ]--
do $$
begin
    -- Verify required extensions
    if not exists (select 1 from pg_extension where extname = 'uuid-ossp') then
        raise exception 'Required extension uuid-ossp is not installed';
    end if;
    
    -- Verify auth schema exists
    if not exists (select 1 from information_schema.schemata where schema_name = 'auth') then
        raise exception 'Required auth schema is not available';
    end if;
    
    -- Verify storage schema exists
    if not exists (select 1 from information_schema.schemata where schema_name = 'storage') then
        raise exception 'Required storage schema is not available';
    end if;
end $$;

--[ 1. Extensions ]--
create extension if not exists "uuid-ossp";

-- Create rpc schema if it doesn't exist
create schema if not exists rpc;
grant usage on schema rpc to anon, authenticated;

-- Create exec_sql function for admin operations
create or replace function exec_sql(query text)
returns void as $$
begin
    execute query;
end;
$$ language plpgsql security definer;

-- Create updated_at trigger function
create or replace function public.handle_updated_at()
returns trigger as $$
begin
    new.updated_at = timezone('utc'::text, now());
    return new;
end;
$$ language plpgsql security definer;

-- Create completed_at trigger function for document artefacts
create or replace function public.set_completed_at()
returns trigger as $$
begin
    if NEW.status = 'completed' and OLD.status != 'completed' then
        NEW.completed_at = now();
    end if;
    return NEW;
end;
$$ language plpgsql security definer;

--[ 5. Core Tables ]--
-- Base user profiles
create table if not exists public.profiles (
    id uuid primary key references auth.users(id) on delete cascade,
    email text not null unique,
    user_type text not null check (
        user_type in (
            'teacher',
            'student',
            'email_teacher',
            'email_student',
            'developer',
            'superadmin'
        )
    ),
    username text not null unique,
    full_name text,
    display_name text,
    metadata jsonb default '{}'::jsonb,
    user_db_name text,
    school_db_name text,
    neo4j_sync_status text default 'pending' check (neo4j_sync_status in ('pending', 'ready', 'failed')),
    neo4j_synced_at timestamp with time zone,
    last_login timestamp with time zone,
    created_at timestamp with time zone default timezone('utc'::text, now()),
    updated_at timestamp with time zone default timezone('utc'::text, now())
);
comment on table public.profiles is 'User profiles linked to Supabase auth.users';
comment on column public.profiles.user_type is 'Type of user: teacher or student';

-- Active institutes
create table if not exists public.institutes (
    id uuid primary key default uuid_generate_v4(),
    name text not null,
    urn text unique,
    status text not null default 'active' check (status in ('active', 'inactive', 'pending')),
    address jsonb default '{}'::jsonb,
    website text,
    metadata jsonb default '{}'::jsonb,
    geo_coordinates jsonb default '{}'::jsonb,
    neo4j_uuid_string text,
    neo4j_public_sync_status text default 'pending' check (neo4j_public_sync_status in ('pending', 'synced', 'failed')),
    neo4j_public_sync_at timestamp with time zone,
    neo4j_private_sync_status text default 'not_started' check (neo4j_private_sync_status in ('not_started', 'pending', 'synced', 'failed')),
    neo4j_private_sync_at timestamp with time zone,
    created_at timestamp with time zone default timezone('utc'::text, now()),
    updated_at timestamp with time zone default timezone('utc'::text, now())
);
comment on table public.institutes is 'Active institutes in the system';
comment on column public.institutes.geo_coordinates is 'Geospatial coordinates from OSM search (latitude, longitude, boundingbox)';

--[ 6. neoFS Filesystem Tables ]--
-- File cabinets for organizing files
create table if not exists public.file_cabinets (
    id uuid primary key default uuid_generate_v4(),
    user_id uuid not null references public.profiles(id) on delete cascade,
    name text not null,
    created_at timestamp with time zone default timezone('utc'::text, now())
);
comment on table public.file_cabinets is 'User file cabinets for organizing documents and files';

-- Files stored in cabinets
create table if not exists public.files (
    id uuid primary key default uuid_generate_v4(),
    cabinet_id uuid not null references public.file_cabinets(id) on delete cascade,
    name text not null,
    path text not null,
    bucket text default 'file-cabinets' not null,
    created_at timestamp with time zone default timezone('utc'::text, now()),
    mime_type text,
    metadata jsonb default '{}'::jsonb,
    size text,
    category text generated always as (
        case
            when mime_type like 'image/%' then 'image'
            when mime_type = 'application/pdf' then 'document'
            when mime_type in ('application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') then 'document'
            when mime_type in ('application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') then 'spreadsheet'
            when mime_type in ('application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation') then 'presentation'
            when mime_type like 'audio/%' then 'audio'
            when mime_type like 'video/%' then 'video'
            else 'other'
        end
    ) stored
);
comment on table public.files is 'Files stored in user cabinets with automatic categorization';
comment on column public.files.category is 'Automatically determined file category based on MIME type';

-- AI brains for processing files
create table if not exists public.brains (
    id uuid primary key default uuid_generate_v4(),
    user_id uuid not null references public.profiles(id) on delete cascade,
    name text not null,
    purpose text,
    created_at timestamp with time zone default timezone('utc'::text, now())
);
comment on table public.brains is 'AI brains for processing and analyzing user files';

-- Brain-file associations
create table if not exists public.brain_files (
    brain_id uuid not null references public.brains(id) on delete cascade,
    file_id uuid not null references public.files(id) on delete cascade,
    primary key (brain_id, file_id)
);
comment on table public.brain_files is 'Associations between AI brains and files for processing';

-- Document artefacts from file processing
create table if not exists public.document_artefacts (
    id uuid primary key default uuid_generate_v4(),
    file_id uuid references public.files(id) on delete cascade,
    page_number integer default 0 not null,
    type text not null,
    rel_path text not null,
    size_tag text,
    language text,
    chunk_index integer,
    extra jsonb,
    created_at timestamp with time zone default timezone('utc'::text, now()),
    status text default 'completed' not null check (status in ('pending', 'processing', 'completed', 'failed')),
    started_at timestamp with time zone default timezone('utc'::text, now()),
    completed_at timestamp with time zone,
    error_message text
);
comment on table public.document_artefacts is 'Extracted artefacts from document processing';
comment on column public.document_artefacts.status is 'Extraction status: pending, processing, completed, or failed';
comment on column public.document_artefacts.started_at is 'Timestamp when extraction process started';
comment on column public.document_artefacts.completed_at is 'Timestamp when extraction process completed (success or failure)';
comment on column public.document_artefacts.error_message is 'Error details if extraction failed';

-- Function execution logs
create table if not exists public.function_logs (
    id serial primary key,
    file_id uuid references public.files(id) on delete cascade,
    timestamp timestamp with time zone default timezone('utc'::text, now()),
    step text,
    message text,
    data jsonb
);
comment on table public.function_logs is 'Logs of function executions and processing steps';

--[ 7. Relationship Tables ]--
-- Institute memberships
create table if not exists public.institute_memberships (
    id uuid primary key default uuid_generate_v4(),
    profile_id uuid references public.profiles(id) on delete cascade,
    institute_id uuid references public.institutes(id) on delete cascade,
    role text not null check (role in ('teacher', 'student')),
    tldraw_preferences jsonb default '{}'::jsonb,
    metadata jsonb default '{}'::jsonb,
    created_at timestamp with time zone default timezone('utc'::text, now()),
    updated_at timestamp with time zone default timezone('utc'::text, now()),
    unique(profile_id, institute_id)
);
comment on table public.institute_memberships is 'Manages user roles and relationships with institutes';

-- Membership requests
create table if not exists public.institute_membership_requests (
    id uuid primary key default uuid_generate_v4(),
    profile_id uuid references public.profiles(id) on delete cascade,
    institute_id uuid references public.institutes(id) on delete cascade,
    requested_role text check (requested_role in ('teacher', 'student')),
    status text default 'pending' check (status in ('pending', 'approved', 'rejected')),
    metadata jsonb default '{}'::jsonb,
    created_at timestamp with time zone default timezone('utc'::text, now()),
    updated_at timestamp with time zone default timezone('utc'::text, now())
);
comment on table public.institute_membership_requests is 'Tracks requests to join institutes';

--[ 8. Audit Tables ]--
-- System audit logs
create table if not exists public.audit_logs (
    id uuid primary key default uuid_generate_v4(),
    profile_id uuid references public.profiles(id) on delete set null,
    action_type text,
    table_name text,
    record_id uuid,
    changes jsonb,
    created_at timestamp with time zone default timezone('utc'::text, now())
);
comment on table public.audit_logs is 'System-wide audit trail for important operations';

--[ 9. Exam Specifications ]--
create table if not exists public.eb_specifications (
    id uuid primary key default uuid_generate_v4(),
    spec_code text unique,
    exam_board_code text,
    award_code text,
    subject_code text,
    first_teach text,
    spec_ver text,
    
    -- Document storage details
    storage_loc text,
    doc_type text check (doc_type in ('pdf', 'json', 'md', 'html', 'txt', 'doctags')),
    doc_details jsonb default '{}'::jsonb, -- e.g. Tika extract
    docling_docs jsonb default '{}'::jsonb, -- e.g. Docling extracts settings and storage locations
    
    created_at timestamp with time zone default timezone('utc'::text, now()),
    updated_at timestamp with time zone default timezone('utc'::text, now())
);

comment on table public.eb_specifications is 'Exam board specifications and their primary document';
comment on column public.eb_specifications.spec_code is 'Unique code for the specification, used for linking exams';
comment on column public.eb_specifications.doc_details is 'Tika extract of the specification document';
comment on column public.eb_specifications.docling_docs is 'Docling extracts settings and storage locations for the specification document';

--[ 10. Exam Papers / Entries ]--
create table if not exists public.eb_exams (
    id uuid primary key default uuid_generate_v4(),
    exam_code text unique,
    spec_code text references public.eb_specifications(spec_code) on delete cascade,
    paper_code text,
    tier text,
    session text,
    type_code text,
    
    -- Document storage details
    storage_loc text,
    doc_type text check (doc_type in ('pdf', 'json', 'md', 'html', 'txt', 'doctags')),
    doc_details jsonb default '{}'::jsonb, -- e.g. Tika extract
    docling_docs jsonb default '{}'::jsonb, -- e.g. Docling extracts settings and storage locations
    
    created_at timestamp with time zone default timezone('utc'::text, now()),
    updated_at timestamp with time zone default timezone('utc'::text, now())
);

comment on table public.eb_exams is 'Exam papers and related documents linked to specifications';
comment on column public.eb_exams.exam_code is 'Unique code for the exam paper, used for linking questions';
comment on column public.eb_exams.type_code is 'Type code for the exam document: Question Paper (QP), Mark Scheme (MS), Examiner Report (ER), Other (OT)';
comment on column public.eb_exams.doc_details is 'Tika extract of the exam paper document';
comment on column public.eb_exams.docling_docs is 'Docling extracts settings and storage locations for the exam paper document';

--[ 11. Indexes ]--
-- Index for geospatial queries
create index if not exists idx_institutes_geo_coordinates on public.institutes using gin(geo_coordinates);
create index if not exists idx_institutes_urn on public.institutes(urn); 

-- Document artefacts indexes
create index if not exists idx_document_artefacts_file_status on public.document_artefacts(file_id, status);
create index if not exists idx_document_artefacts_file_type on public.document_artefacts(file_id, type);
create index if not exists idx_document_artefacts_status on public.document_artefacts(status);

-- File indexes
create index if not exists idx_files_cabinet_id on public.files(cabinet_id);
create index if not exists idx_files_mime_type on public.files(mime_type);
create index if not exists idx_files_category on public.files(category);

-- Brain indexes
create index if not exists idx_brains_user_id on public.brains(user_id);

-- Exam board indexes
create index if not exists idx_eb_exams_exam_code on public.eb_exams(exam_code);
create index if not exists idx_eb_exams_spec_code on public.eb_exams(spec_code);
create index if not exists idx_eb_exams_paper_code on public.eb_exams(paper_code);
create index if not exists idx_eb_exams_tier on public.eb_exams(tier);
create index if not exists idx_eb_exams_session on public.eb_exams(session);
create index if not exists idx_eb_exams_type_code on public.eb_exams(type_code);
create index if not exists idx_eb_specifications_spec_code on public.eb_specifications(spec_code);
create index if not exists idx_eb_specifications_exam_board_code on public.eb_specifications(exam_board_code);
create index if not exists idx_eb_specifications_award_code on public.eb_specifications(award_code);
create index if not exists idx_eb_specifications_subject_code on public.eb_specifications(subject_code);

--[ 12. Triggers ]--
-- Set completed_at when document artefact status changes to completed
create trigger trigger_set_completed_at
    before update on public.document_artefacts
    for each row
    execute function public.set_completed_at();

-- Set updated_at on profile updates
create trigger trigger_profiles_updated_at
    before update on public.profiles
    for each row
    execute function public.handle_updated_at();

-- Set updated_at on institute updates
create trigger trigger_institutes_updated_at
    before update on public.institutes
    for each row
    execute function public.handle_updated_at();

-- Set updated_at on institute_memberships updates
create trigger trigger_institute_memberships_updated_at
    before update on public.institute_memberships
    for each row
    execute function public.handle_updated_at();

-- Set updated_at on institute_membership_requests updates
create trigger trigger_institute_membership_requests_updated_at
    before update on public.institute_memberships
    for each row
    execute function public.handle_updated_at();

-- Set updated_at on eb_specifications updates
create trigger trigger_eb_specifications_updated_at
    before update on public.eb_specifications
    for each row
    execute function public.handle_updated_at();

-- Set updated_at on eb_exams updates
create trigger trigger_eb_exams_updated_at
    before update on public.eb_exams
    for each row
    execute function public.handle_updated_at();
