-- Baseline Migration: Complete database schema
-- This migration represents the current state of the database schema
-- For existing databases, use: migrate force 1 (do not run this migration)
-- For new databases, this will create the complete schema

-- ============================================
-- Core Tables (no dependencies)
-- ============================================

-- Users table (base for multi-tenancy)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    name VARCHAR(255),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE,
    last_login_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX idx_users_email ON users(email);

-- Dictionaries table
CREATE TABLE dictionaries (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_dictionaries_user_id ON dictionaries(user_id);

-- Insert default dictionary with id = 0
DO $$
BEGIN
    -- Set sequence to -1 so next value will be 0
    PERFORM setval('dictionaries_id_seq', -1, false);
    
    -- Insert the default dictionary with id = 0
    INSERT INTO dictionaries (id, name) 
    VALUES (0, 'Все слова')
    ON CONFLICT (id) DO NOTHING;
    
    -- Set the sequence to start from 1 (so next auto-increment will be 1)
    PERFORM setval('dictionaries_id_seq', 1, false);
EXCEPTION
    WHEN others THEN
        -- If sequence doesn't exist or other error, try without sequence manipulation
        INSERT INTO dictionaries (id, name) 
        VALUES (0, 'Все слова')
        ON CONFLICT (id) DO NOTHING;
END $$;

-- Projects table
CREATE TABLE projects (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    priority SMALLINT,
    deleted BOOLEAN NOT NULL DEFAULT FALSE,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_projects_deleted ON projects(deleted);
CREATE INDEX idx_projects_user_id ON projects(user_id);

-- Entries table
CREATE TABLE entries (
    id SERIAL PRIMARY KEY,
    text TEXT NOT NULL,
    created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_entries_user_id ON entries(user_id);

-- ============================================
-- Dependent Tables
-- ============================================

-- Words table (depends on dictionaries, users)
CREATE TABLE words (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    translation TEXT NOT NULL,
    description TEXT,
    dictionary_id INTEGER NOT NULL DEFAULT 0 REFERENCES dictionaries(id),
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_words_dictionary_id ON words(dictionary_id);
CREATE INDEX idx_words_user_id ON words(user_id);

-- Progress table (depends on words, users)
CREATE TABLE progress (
    id SERIAL PRIMARY KEY,
    word_id INTEGER NOT NULL REFERENCES words(id) ON DELETE CASCADE,
    success INTEGER DEFAULT 0,
    failure INTEGER DEFAULT 0,
    last_success_at TIMESTAMP,
    last_failure_at TIMESTAMP,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    CONSTRAINT progress_word_user_unique UNIQUE (word_id, user_id)
);

CREATE INDEX idx_progress_user_id ON progress(user_id);
CREATE UNIQUE INDEX idx_progress_word_user_unique ON progress(word_id, user_id);

-- Configs table (depends on users)
CREATE TABLE configs (
    id SERIAL PRIMARY KEY,
    words_count INTEGER NOT NULL,
    max_cards INTEGER,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_configs_user_id ON configs(user_id);

-- Config dictionaries table (depends on configs, dictionaries)
CREATE TABLE config_dictionaries (
    config_id INTEGER NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
    dictionary_id INTEGER NOT NULL REFERENCES dictionaries(id) ON DELETE CASCADE,
    PRIMARY KEY (config_id, dictionary_id)
);

CREATE INDEX idx_config_dictionaries_config_id ON config_dictionaries(config_id);
CREATE INDEX idx_config_dictionaries_dictionary_id ON config_dictionaries(dictionary_id);

-- Nodes table (depends on projects, entries, users)
CREATE TABLE nodes (
    id SERIAL PRIMARY KEY,
    project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
    score NUMERIC(8,4),
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_nodes_project_id ON nodes(project_id);
CREATE INDEX idx_nodes_entry_id ON nodes(entry_id);
CREATE INDEX idx_nodes_user_id ON nodes(user_id);

-- Weekly goals table (depends on projects, users)
CREATE TABLE weekly_goals (
    id SERIAL PRIMARY KEY,
    project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    goal_year INTEGER NOT NULL,
    goal_week INTEGER NOT NULL,
    min_goal_score NUMERIC(10,4) NOT NULL DEFAULT 0,
    max_goal_score NUMERIC(10,4),
    max_score NUMERIC(10,4),
    priority SMALLINT,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    CONSTRAINT weekly_goals_project_id_goal_year_goal_week_key UNIQUE (project_id, goal_year, goal_week)
);

CREATE INDEX idx_weekly_goals_project_id ON weekly_goals(project_id);
CREATE INDEX idx_weekly_goals_user_id ON weekly_goals(user_id);

-- Tasks table (depends on users)
CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    completed INTEGER DEFAULT 0,
    last_completed_at TIMESTAMP WITH TIME ZONE,
    parent_task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
    reward_message TEXT,
    progression_base NUMERIC(10,4),
    deleted BOOLEAN DEFAULT FALSE,
    repetition_period INTERVAL,
    next_show_at TIMESTAMP WITH TIME ZONE,
    repetition_date TEXT,
    config_id INTEGER REFERENCES configs(id) ON DELETE SET NULL,
    wishlist_id INTEGER,
    reward_policy VARCHAR(20) DEFAULT 'personal'
);

CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_parent_task_id ON tasks(parent_task_id);
CREATE INDEX idx_tasks_deleted ON tasks(deleted);
CREATE INDEX idx_tasks_last_completed_at ON tasks(last_completed_at);
CREATE INDEX idx_tasks_config_id ON tasks(config_id);
CREATE UNIQUE INDEX idx_tasks_config_id_unique ON tasks(config_id) WHERE config_id IS NOT NULL AND deleted = FALSE;
CREATE INDEX idx_tasks_wishlist_id ON tasks(wishlist_id);
CREATE UNIQUE INDEX idx_tasks_wishlist_id_unique ON tasks(wishlist_id) WHERE wishlist_id IS NOT NULL AND deleted = FALSE;
CREATE INDEX idx_tasks_id_user_deleted ON tasks(id, user_id, deleted) WHERE deleted = FALSE;
CREATE INDEX idx_tasks_parent_deleted_covering ON tasks(parent_task_id, deleted, id) 
    INCLUDE (name, completed, last_completed_at, reward_message, progression_base) 
    WHERE deleted = FALSE;

COMMENT ON COLUMN tasks.config_id IS 'Link to test config. NULL if task is not a test.';
COMMENT ON COLUMN tasks.reward_policy IS 'For wishlist tasks: personal = only if user completes, shared = anyone completes';

-- Reward configs table (depends on tasks, projects)
CREATE TABLE reward_configs (
    id SERIAL PRIMARY KEY,
    position INTEGER NOT NULL,
    task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
    project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
    value NUMERIC(10,4) NOT NULL,
    use_progression BOOLEAN DEFAULT FALSE
);

CREATE INDEX idx_reward_configs_task_id ON reward_configs(task_id);
CREATE INDEX idx_reward_configs_project_id ON reward_configs(project_id);
CREATE UNIQUE INDEX idx_reward_configs_task_position ON reward_configs(task_id, position);

-- Telegram integrations table (depends on users)
CREATE TABLE telegram_integrations (
    id SERIAL PRIMARY KEY,
    chat_id VARCHAR(255),
    telegram_user_id BIGINT,
    start_token VARCHAR(255),
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX idx_telegram_integrations_user_id_unique ON telegram_integrations(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX idx_telegram_integrations_user_id ON telegram_integrations(user_id);
CREATE UNIQUE INDEX idx_telegram_integrations_start_token ON telegram_integrations(start_token) WHERE start_token IS NOT NULL;
CREATE UNIQUE INDEX idx_telegram_integrations_telegram_user_id ON telegram_integrations(telegram_user_id) WHERE telegram_user_id IS NOT NULL;
CREATE INDEX idx_telegram_integrations_chat_id ON telegram_integrations(chat_id) WHERE chat_id IS NOT NULL;

-- Todoist integrations table (depends on users)
CREATE TABLE todoist_integrations (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    todoist_user_id BIGINT,
    todoist_email VARCHAR(255),
    access_token TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT todoist_integrations_user_id_unique UNIQUE (user_id)
);

CREATE INDEX idx_todoist_integrations_user_id ON todoist_integrations(user_id);
CREATE UNIQUE INDEX idx_todoist_integrations_todoist_user_id ON todoist_integrations(todoist_user_id) WHERE todoist_user_id IS NOT NULL;
CREATE UNIQUE INDEX idx_todoist_integrations_todoist_email ON todoist_integrations(todoist_email) WHERE todoist_email IS NOT NULL;

-- Wishlist boards table (depends on users)
CREATE TABLE wishlist_boards (
    id SERIAL PRIMARY KEY,
    owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    invite_token VARCHAR(64) UNIQUE,
    invite_enabled BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    deleted BOOLEAN DEFAULT FALSE
);

CREATE INDEX idx_wishlist_boards_owner_id ON wishlist_boards(owner_id);
CREATE INDEX idx_wishlist_boards_invite_token ON wishlist_boards(invite_token) WHERE invite_token IS NOT NULL;
CREATE INDEX idx_wishlist_boards_owner_deleted ON wishlist_boards(owner_id, deleted);

-- Wishlist board members table (depends on wishlist_boards, users)
CREATE TABLE wishlist_board_members (
    id SERIAL PRIMARY KEY,
    board_id INTEGER NOT NULL REFERENCES wishlist_boards(id) ON DELETE CASCADE,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT unique_board_member UNIQUE (board_id, user_id)
);

CREATE INDEX idx_board_members_board_id ON wishlist_board_members(board_id);
CREATE INDEX idx_board_members_user_id ON wishlist_board_members(user_id);

-- Wishlist items table (depends on users, wishlist_boards)
CREATE TABLE wishlist_items (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    price NUMERIC(10,2),
    image_path VARCHAR(500),
    link TEXT,
    completed BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    deleted BOOLEAN DEFAULT FALSE,
    board_id INTEGER REFERENCES wishlist_boards(id) ON DELETE CASCADE,
    author_id INTEGER REFERENCES users(id) ON DELETE SET NULL
);

CREATE INDEX idx_wishlist_items_user_id ON wishlist_items(user_id);
CREATE INDEX idx_wishlist_items_user_deleted ON wishlist_items(user_id, deleted);
CREATE INDEX idx_wishlist_items_user_completed ON wishlist_items(user_id, completed, deleted);
CREATE INDEX idx_wishlist_items_board_id ON wishlist_items(board_id);
CREATE INDEX idx_wishlist_items_author_id ON wishlist_items(author_id);
CREATE INDEX idx_wishlist_items_id_deleted_covering ON wishlist_items(id, deleted) 
    INCLUDE (name) 
    WHERE deleted = FALSE;

-- Add foreign key for tasks.wishlist_id after wishlist_items is created
ALTER TABLE tasks ADD CONSTRAINT tasks_wishlist_id_fkey 
    FOREIGN KEY (wishlist_id) REFERENCES wishlist_items(id) ON DELETE SET NULL;

COMMENT ON TABLE wishlist_items IS 'Wishlist items for users';
COMMENT ON COLUMN wishlist_items.completed IS 'Flag indicating item was purchased/received';
COMMENT ON COLUMN wishlist_items.image_path IS 'Path to image file relative to uploads root';

-- Task conditions table (depends on tasks)
CREATE TABLE task_conditions (
    id SERIAL PRIMARY KEY,
    task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT unique_task_condition UNIQUE (task_id)
);

CREATE INDEX idx_task_conditions_task_id ON task_conditions(task_id);

COMMENT ON TABLE task_conditions IS 'Reusable unlock conditions based on task completion';

-- Score conditions table (depends on projects, users)
CREATE TABLE score_conditions (
    id SERIAL PRIMARY KEY,
    project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    required_points NUMERIC(10,4) NOT NULL,
    start_date DATE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    CONSTRAINT unique_score_condition UNIQUE (project_id, required_points, start_date)
);

CREATE INDEX idx_score_conditions_project_id ON score_conditions(project_id);
CREATE INDEX idx_score_conditions_user_id ON score_conditions(user_id);

COMMENT ON TABLE score_conditions IS 'Reusable unlock conditions based on project points';
COMMENT ON COLUMN score_conditions.start_date IS 'Date from which to start counting points. NULL means count all time.';

-- Wishlist conditions table (depends on wishlist_items, task_conditions, score_conditions, users)
CREATE TABLE wishlist_conditions (
    id SERIAL PRIMARY KEY,
    wishlist_item_id INTEGER NOT NULL REFERENCES wishlist_items(id) ON DELETE CASCADE,
    task_condition_id INTEGER REFERENCES task_conditions(id) ON DELETE CASCADE,
    score_condition_id INTEGER REFERENCES score_conditions(id) ON DELETE CASCADE,
    display_order INTEGER DEFAULT 0,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    CONSTRAINT check_exactly_one_condition CHECK (
        (task_condition_id IS NOT NULL AND score_condition_id IS NULL) OR
        (task_condition_id IS NULL AND score_condition_id IS NOT NULL)
    )
);

CREATE INDEX idx_wishlist_conditions_item_id ON wishlist_conditions(wishlist_item_id);
CREATE INDEX idx_wishlist_conditions_item_order ON wishlist_conditions(wishlist_item_id, display_order);
CREATE INDEX idx_wishlist_conditions_task_condition_id ON wishlist_conditions(task_condition_id);
CREATE INDEX idx_wishlist_conditions_score_condition_id ON wishlist_conditions(score_condition_id);
CREATE INDEX idx_wishlist_conditions_user_id ON wishlist_conditions(user_id);

COMMENT ON TABLE wishlist_conditions IS 'Links between wishlist items and unlock conditions. Multiple conditions per item use AND logic.';
COMMENT ON COLUMN wishlist_conditions.display_order IS 'Order for displaying conditions in UI';
COMMENT ON COLUMN wishlist_conditions.user_id IS 'Owner of this condition. Each user has their own goals on shared boards';

-- Refresh tokens table (depends on users)
CREATE TABLE refresh_tokens (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_token_hash ON refresh_tokens(token_hash);

-- ============================================
-- Materialized Views
-- ============================================

-- Weekly report materialized view
CREATE MATERIALIZED VIEW weekly_report_mv AS
SELECT
    p.id AS project_id,
    agg.report_year,
    agg.report_week,
    COALESCE(agg.total_score, 0.0000) AS total_score,
    CASE
        WHEN wg.max_score IS NULL THEN COALESCE(agg.total_score, 0.0000)
        ELSE LEAST(COALESCE(agg.total_score, 0.0000), wg.max_score)
    END AS normalized_total_score
FROM
    projects p
LEFT JOIN 
    (
        SELECT
            n.project_id,
            EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year,
            EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week,
            SUM(n.score) AS total_score
        FROM
            nodes n
        JOIN
            entries e ON n.entry_id = e.id
        GROUP BY
            1, 2, 3
    ) agg 
    ON p.id = agg.project_id
LEFT JOIN
    weekly_goals wg
    ON wg.project_id = p.id
    AND wg.goal_year = agg.report_year
    AND wg.goal_week = agg.report_week
WHERE
    p.deleted = FALSE
ORDER BY
    p.id, agg.report_year, agg.report_week
WITH DATA;

CREATE INDEX idx_weekly_report_mv_project_year_week ON weekly_report_mv(project_id, report_year, report_week);

COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project using ISOYEAR for correct week calculations at year boundaries. Includes all projects via LEFT JOIN. Adds normalized_total_score using weekly_goals.max_score snapshot.';

-- ============================================
-- Comments
-- ============================================

COMMENT ON TABLE configs IS 'Test configurations (words_count, max_cards, dictionary associations). Linked to tasks via tasks.config_id.';
COMMENT ON TABLE wishlist_boards IS 'Wishlist boards for organizing and sharing wishes';
COMMENT ON COLUMN wishlist_boards.invite_token IS 'Token for invite link, NULL = disabled';
COMMENT ON COLUMN wishlist_boards.invite_enabled IS 'Whether invite link is active';
COMMENT ON TABLE wishlist_board_members IS 'Users who joined boards via invite link (not owners)';
COMMENT ON COLUMN wishlist_items.author_id IS 'User who created this item (may differ from board owner on shared boards)';
COMMENT ON COLUMN wishlist_items.board_id IS 'Board this item belongs to';

-- ============================================
-- Additional Tables
-- ============================================

-- Eateries table
CREATE TABLE eateries (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    address VARCHAR(255),
    type VARCHAR(50),
    distance DOUBLE PRECISION
);

-- Interesting places table
CREATE TABLE interesting_places (
    id INTEGER PRIMARY KEY,
    name TEXT,
    city TEXT,
    description TEXT,
    added_at TIMESTAMP WITH TIME ZONE,
    is_visited BOOLEAN,
    phone_number TEXT,
    address TEXT,
    updated_at TIMESTAMP WITH TIME ZONE
);

-- Music groups table
CREATE TABLE music_groups (
    id INTEGER PRIMARY KEY,
    name TEXT,
    possible_locations TEXT
);

-- N8N chat histories table
CREATE TABLE n8n_chat_histories (
    id SERIAL PRIMARY KEY,
    session_id VARCHAR(255) NOT NULL,
    message JSONB NOT NULL
);

-- Places to visit table
CREATE TABLE places_to_visit (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    city TEXT,
    description TEXT,
    added_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    is_visited BOOLEAN DEFAULT FALSE,
    phone_number TEXT,
    address TEXT,
    updated_at TIMESTAMP WITH TIME ZONE
);

-- Restaurants table
CREATE TABLE restaurants (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    address VARCHAR(255),
    contact_info VARCHAR(255)
);

-- Upcoming concerts table (depends on music_groups)
CREATE TABLE upcoming_concerts (
    id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    group_id INTEGER NOT NULL REFERENCES music_groups(id),
    scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL,
    venue TEXT,
    city TEXT,
    tickets_url TEXT
);

CREATE UNIQUE INDEX idx_unique_concert ON upcoming_concerts(scheduled_at, city, group_id);
