-- =======================================
-- NAIL SALON DATABASE SCHEMA
-- Complete init.sql with GDPR & Tax Compliance
-- =======================================

-- Enable pgcrypto for generating random IDs
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- =======================================
-- ENUMS
-- =======================================

CREATE TYPE account_role AS ENUM ('unverified_email', 'verified_email', 'admin', 'guest', 'affiliate');
CREATE TYPE account_type AS ENUM ('email', 'google', 'microsoft', 'facebook', 'guest');
CREATE TYPE payment_type AS ENUM ('deposit', 'full', 'tip', 'balance', 'partial');
CREATE TYPE payment_method AS ENUM ('online_square', 'in_person_card', 'cash', 'giftcard', 'discount');
CREATE TYPE payment_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
CREATE TYPE booking_status AS ENUM ('pending', 'confirmed', 'in_progress', 'completed', 'client_cancelled', 'we_cancelled', 're-schedule', 'no_show', 'no_deposit');
CREATE TYPE campaign_type AS ENUM ('time_based', 'milestone');
CREATE TYPE milestone_type AS ENUM ('per_user_booking_count', 'global_booking_count', 'anniversary');
CREATE TYPE milestone_unit AS ENUM ('bookings', 'months', 'years');
CREATE TYPE discount_campaign_scope AS ENUM ('all_bookings', 'first_booking_only', 'new_customers_only');
CREATE TYPE discount_campaign_status AS ENUM ('draft', 'active', 'completed', 'cancelled');

-- =======================================
-- SHORT ID GENERATION
-- =======================================

CREATE OR REPLACE FUNCTION generate_short_id(table_name TEXT)
RETURNS CHAR(12) AS $$
DECLARE
    new_id CHAR(12);
    collision_count INT := 0;
    max_attempts INT := 100;
BEGIN
    LOOP
        new_id := substr(encode(gen_random_bytes(6), 'hex'), 1, 12);

        EXECUTE format('SELECT 1 FROM %I WHERE id = $1 LIMIT 1', table_name) USING new_id;

        IF NOT FOUND THEN
            RETURN new_id;
        END IF;

        collision_count := collision_count + 1;
        IF collision_count >= max_attempts THEN
            RAISE EXCEPTION 'Unable to generate unique ID after % attempts for table %', max_attempts, table_name;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION generate_user_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('users'); $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_service_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('services'); $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_booking_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('bookings'); $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_payment_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('payments'); $$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION generate_verification_code() RETURNS CHAR(12) AS $$ SELECT substr(encode(gen_random_bytes(6), 'hex'), 1, 12); $$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION generate_referral_code()
RETURNS CHAR(12) AS $$
DECLARE
    new_code CHAR(12);
    collision_count INT := 0;
    max_attempts INT := 100;
BEGIN
    LOOP
        new_code := substr(encode(gen_random_bytes(6), 'hex'), 1, 12);

        -- Check against referral_code column in users table (not id column)
        PERFORM 1 FROM users WHERE referral_code = new_code LIMIT 1;

        IF NOT FOUND THEN
            RETURN new_code;
        END IF;

        collision_count := collision_count + 1;
        IF collision_count >= max_attempts THEN
            RAISE EXCEPTION 'Unable to generate unique referral code after % attempts', max_attempts;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

-- =======================================
-- USERS TABLE
-- =======================================

CREATE TABLE users (
    id CHAR(12) PRIMARY KEY DEFAULT generate_user_id(),
    -- Identity fields
    n_first_name VARCHAR(50) NOT NULL,          -- vCard N.given
    n_last_name VARCHAR(50) NOT NULL,           -- vCard N.family
    fn VARCHAR(120) GENERATED ALWAYS AS (n_first_name || ' ' || n_last_name) STORED, -- vCard FN
    email VARCHAR(255),                 -- vCard EMAIL (nullable for social-only/guest)
                                       -- Uniqueness enforced only for non-guest users via partial index
    phone VARCHAR(20) NOT NULL,                         -- vCard TEL
    date_of_birth DATE NOT NULL,                        -- vCard BDAY
    profile_pic_url TEXT,                      -- vCard PHOTO
    -- Account fields
    account_role account_role NOT NULL DEFAULT 'unverified_email', -- initial signup role
    account_type account_type NOT NULL DEFAULT 'email', -- initial signup type (info only)
    -- Security fields
    password_hash TEXT,                         -- NULL for pure social logins
    last_login_at TIMESTAMPTZ DEFAULT NOW(),
    -- Loyalty fields
    loyalty_stamps INT NOT NULL DEFAULT 0,
    referral_code CHAR(12) UNIQUE DEFAULT generate_referral_code(),
    -- GDPR fields
    privacy_policy_and_terms_consent BOOLEAN NOT NULL DEFAULT TRUE,
    policy_consent_updated_at TIMESTAMPTZ DEFAULT NOW(),
    data_retention_consent BOOLEAN NOT NULL DEFAULT TRUE,
    data_consent_updated_at TIMESTAMPTZ DEFAULT NOW(),
    -- Audit fields
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    -- Deposit tracking: remaining deposits needed (0-3). Reduces by 1 when booking with payment completes.
    deposits_required INT NOT NULL DEFAULT 0,
    -- staff fields
    notes TEXT
);

CREATE TABLE user_social_logins (
    id BIGSERIAL PRIMARY KEY,
    user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    provider account_type NOT NULL CHECK (provider IN ('google', 'microsoft', 'facebook')),
    immutable_id TEXT NOT NULL,                  -- the stable provider user ID
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    UNIQUE (provider, immutable_id),             -- prevents duplicate identities
    UNIQUE (user_id, provider)                   -- one account per provider per user
);


CREATE INDEX idx_users_email_lower ON users (LOWER(email));
CREATE INDEX idx_users_account_role ON users (account_role);

-- Enforce unique email only for registered (non-guest) users
-- Guest accounts can share emails; registered accounts cannot
CREATE UNIQUE INDEX idx_users_email_registered ON users (email) WHERE account_role != 'guest';

-- =======================================
-- VERIFICATION CODES TABLE
-- =======================================
CREATE TYPE verification_purpose AS ENUM ('email_verify', 'password_reset');

CREATE TABLE verification_codes (
    id BIGSERIAL PRIMARY KEY,
    code CHAR(12) NOT NULL UNIQUE DEFAULT generate_verification_code(),
    user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    purpose verification_purpose NOT NULL,
    expires_at TIMESTAMPTZ NOT NULL,
    used_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_verification_codes_code ON verification_codes (code);
CREATE INDEX idx_verification_codes_user_purpose ON verification_codes (user_id, purpose) WHERE used_at IS NULL;
CREATE INDEX idx_verification_codes_expires ON verification_codes (expires_at) WHERE used_at IS NULL;

-- =======================================
-- PATCH TESTS TABLE
-- =======================================

CREATE TABLE patch_tests (
    id CHAR(12) PRIMARY KEY DEFAULT generate_service_id(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    notice_duration_hours INT NOT NULL DEFAULT 24,
    expiry_months INT NOT NULL DEFAULT 6,
    service_ids CHAR(12)[] DEFAULT '{}'
);

CREATE INDEX idx_patch_tests_name ON patch_tests(name);

-- =======================================
-- USER PATCH TESTS TABLE
-- =======================================

CREATE TABLE user_patch_tests (
    id BIGSERIAL PRIMARY KEY,
    user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    patch_test_id CHAR(12) NOT NULL REFERENCES patch_tests(id) ON DELETE CASCADE,
    tested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (user_id, patch_test_id)
);

CREATE INDEX idx_user_patch_tests_user ON user_patch_tests(user_id);
CREATE INDEX idx_user_patch_tests_tested_at ON user_patch_tests(tested_at);

-- =======================================
-- SERVICES TABLE
-- =======================================

CREATE TABLE services (
    id CHAR(12) PRIMARY KEY DEFAULT generate_service_id(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    price NUMERIC(10,2) NOT NULL,
    duration_minutes INT NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    minimum_age_required INT NOT NULL DEFAULT 16,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by CHAR(12)
);

CREATE INDEX idx_services_name ON services(name);

-- =======================================
-- BOOKINGS TABLE
-- =======================================

CREATE TABLE bookings (
    id CHAR(12) PRIMARY KEY DEFAULT generate_booking_id(),
    user_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
    start_time TIMESTAMPTZ NOT NULL,
    status booking_status NOT NULL DEFAULT 'pending',
    notes TEXT,
    deposit_required BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by CHAR(12),
    idempotency_key VARCHAR(64) UNIQUE,
    discount_eligible BOOLEAN NOT NULL DEFAULT FALSE
);

CREATE INDEX idx_bookings_userid ON bookings(user_id);
CREATE INDEX idx_bookings_starttime ON bookings(start_time);
CREATE INDEX idx_bookings_status ON bookings(status);
CREATE INDEX idx_bookings_userid_starttime ON bookings(user_id, start_time);

-- =======================================
-- BOOKING SERVICES JUNCTION TABLE
-- =======================================

CREATE TABLE booking_services (
    booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
    service_id CHAR(12) NOT NULL REFERENCES services(id) ON DELETE RESTRICT,
    override_price NUMERIC(10,2),
    override_duration_minutes INT,
    PRIMARY KEY (booking_id, service_id)
);

-- =======================================
-- BOOKING EDIT REQUESTS TABLE
-- =======================================

CREATE TABLE booking_edit_requests (
    id CHAR(12) PRIMARY KEY DEFAULT generate_booking_id(),
    booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
    requested_by CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    new_start_time TIMESTAMPTZ,
    new_services CHAR(12)[] DEFAULT '{}',  -- Array of service IDs to replace booking_services
    notes TEXT,
    has_overrides BOOLEAN NOT NULL DEFAULT FALSE,  -- If TRUE, cannot change services, use existing overrides for duration
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_at_least_one_field CHECK (
        new_start_time IS NOT NULL OR
        array_length(new_services, 1) IS NOT NULL OR
        notes IS NOT NULL
    )
);

CREATE INDEX idx_booking_edit_requests_booking ON booking_edit_requests(booking_id);

CREATE TABLE user_referrals (
    referrer_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    referred_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    referred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    claimed_booking_id CHAR(12) REFERENCES bookings(id) ON DELETE SET NULL,
    PRIMARY KEY (referrer_id, referred_id)
);

-- =======================================
-- DEFAULT WORKING HOURS TABLE
-- =======================================
CREATE TABLE working_hours (
    weekday SMALLINT PRIMARY KEY,    -- 0 = Monday, 6 = Sunday
    start_time TIME NOT NULL,        -- e.g. 09:00
    end_time TIME NOT NULL,          -- e.g. 17:00
    is_open BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE INDEX idx_working_hours_weekday ON working_hours(weekday);

INSERT INTO working_hours VALUES (0, '00:00:00', '00:00:00', FALSE);
INSERT INTO working_hours VALUES (1, '09:00:00', '17:00:00', TRUE);
INSERT INTO working_hours VALUES (2, '09:00:00', '17:00:00', TRUE);
INSERT INTO working_hours VALUES (3, '12:00:00', '20:00:00', TRUE);
INSERT INTO working_hours VALUES (4, '09:00:00', '17:00:00', TRUE);
INSERT INTO working_hours VALUES (5, '09:00:00', '17:00:00', TRUE);
INSERT INTO working_hours VALUES (6, '00:00:00', '00:00:00', FALSE);

-- =======================================
-- EXCEPTIONAL WORKING HOURS
-- =======================================

-- Exceptional working hours groups (template)
CREATE TABLE exceptional_working_hours_groups (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,             -- e.g. "Christmas Schedule"
    description TEXT NOT NULL       -- e.g. "Extended hours for holiday period"
);

-- Exceptional working hours (7 entries per group, one per weekday)
CREATE TABLE exceptional_working_hours (
    id SERIAL PRIMARY KEY,
    group_id INT NOT NULL REFERENCES exceptional_working_hours_groups(id) ON DELETE CASCADE,
    weekday SMALLINT NOT NULL,      -- 0 = Monday ... 6 = Sunday
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    is_open BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE UNIQUE INDEX idx_group_weekday ON exceptional_working_hours(group_id, weekday);

-- Exceptional group applications (assign a group to a specific week)
CREATE TABLE exceptional_group_applications (
    id SERIAL PRIMARY KEY,
    group_id INT NOT NULL REFERENCES exceptional_working_hours_groups(id) ON DELETE CASCADE,
    week_start DATE NOT NULL        -- Monday of the week this group applies to
);

CREATE UNIQUE INDEX idx_group_application_week ON exceptional_group_applications(week_start);

-- =======================================
-- TIME BLOCKERS TABLE
-- =======================================
-- Admin-defined time periods that are unavailable for booking
-- Used for doctor appointments, extended lunch, training, etc.

CREATE TABLE time_blockers (
    id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('time_blockers'),
    start_time TIMESTAMPTZ NOT NULL,
    duration_minutes INT NOT NULL CHECK (duration_minutes > 0),
    description TEXT,
    cron_expression TEXT,  -- NULL = one-off, otherwise cron pattern for recurring
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by CHAR(12) REFERENCES users(id) ON DELETE SET NULL
);

-- Indexes for efficient overlap queries
CREATE INDEX idx_time_blockers_start_time ON time_blockers(start_time);
CREATE INDEX idx_time_blockers_cron ON time_blockers(cron_expression) WHERE cron_expression IS NOT NULL;

-- =======================================
-- FORGIVEN NO-SHOWS TABLE
-- =======================================
-- Tracks which no-shows have been forgiven (by full deposit payment)
CREATE TABLE forgiven_no_shows (
    id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('forgiven_no_shows'),
    booking_id CHAR(12) NOT NULL UNIQUE REFERENCES bookings(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_forgiven_no_shows_booking_id ON forgiven_no_shows(booking_id);


-- =======================================
-- PAYMENTS TABLE
-- =======================================
-- PAYMENTS TABLE
-- =======================================
CREATE SEQUENCE invoice_number_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

CREATE TABLE payments (
    id CHAR(12) PRIMARY KEY DEFAULT generate_payment_id(),
    booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE RESTRICT,
    payment_type payment_type NOT NULL,
    payment_method payment_method NOT NULL,
    vendor_code TEXT,
    invoice_number INT UNIQUE DEFAULT nextval('invoice_number_seq'),
    status payment_status NOT NULL DEFAULT 'pending',
    amount NUMERIC(10,2) NOT NULL,
    -- VAT fields (NULL until VAT registered - likely won't need but good to be ready for)
    is_vat_applicable BOOLEAN NOT NULL DEFAULT FALSE,
    vat_rate NUMERIC(5,2),
    vat_amount NUMERIC(10,2),
    net_amount NUMERIC(10,2),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by CHAR(12)
);

CREATE INDEX idx_payments_bookingid ON payments(booking_id);
CREATE INDEX idx_payments_status ON payments(status);
CREATE INDEX idx_payments_createdat ON payments(created_at);

-- =======================================
-- LOYALTY REDEMPTIONS TABLE
-- =======================================
CREATE TABLE loyalty_redemptions (
    id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('loyalty_redemptions'),
    user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    stamps_redeemed INT NOT NULL DEFAULT 10,
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    applied_to_booking_id CHAR(12) REFERENCES bookings(id) ON DELETE SET NULL,
    redeemed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    applied_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '6 months')
);

CREATE INDEX idx_loyalty_redemptions_user ON loyalty_redemptions(user_id);
CREATE INDEX idx_loyalty_redemptions_status ON loyalty_redemptions(status);

-- =======================================
-- DISCOUNT CAMPAIGNS TABLE
-- =======================================
CREATE TABLE discount_campaigns (
    id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('discount_campaigns'),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    campaign_type campaign_type NOT NULL DEFAULT 'time_based',
    discount_percent NUMERIC(5,2) NOT NULL,
    scope discount_campaign_scope,
    start_date TIMESTAMPTZ,
    end_date TIMESTAMPTZ,
    milestone_type milestone_type,
    milestone_value INT,
    milestone_unit milestone_unit,
    status discount_campaign_status NOT NULL DEFAULT 'draft',
    max_redemptions INT,
    times_redeemed INT NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
    CONSTRAINT chk_dates CHECK (campaign_type = 'milestone' OR (start_date IS NOT NULL AND end_date IS NOT NULL AND end_date > start_date)),
    CONSTRAINT chk_discount CHECK (discount_percent > 0 AND discount_percent <= 100),
    CONSTRAINT chk_milestone CHECK (campaign_type = 'time_based' OR (milestone_type IS NOT NULL AND milestone_value IS NOT NULL AND (milestone_type != 'anniversary' OR milestone_unit IS NOT NULL)))
);

CREATE INDEX idx_discount_campaigns_dates ON discount_campaigns(start_date, end_date) WHERE start_date IS NOT NULL;
CREATE INDEX idx_discount_campaigns_status ON discount_campaigns(status);
CREATE INDEX idx_discount_campaigns_type ON discount_campaigns(campaign_type);

-- =======================================
-- BOOKING DISCOUNTS TABLE
-- =======================================
CREATE TABLE booking_discounts (
    id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('booking_discounts'),
    booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
    user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    discount_source VARCHAR(30) NOT NULL,
    source_id CHAR(12),
    campaign_type campaign_type,
    milestone_type milestone_type,
    discount_percent NUMERIC(5,2) NOT NULL,
    original_total NUMERIC(10,2) NOT NULL,
    discount_amount NUMERIC(10,2) NOT NULL,
    applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_booking_discounts_booking ON booking_discounts(booking_id);
CREATE INDEX idx_booking_discounts_source ON booking_discounts(discount_source, source_id);
CREATE INDEX idx_booking_discounts_user ON booking_discounts(user_id);
CREATE INDEX idx_booking_discounts_milestone ON booking_discounts(user_id, milestone_type, source_id);

-- =======================================
-- BUSINESS SETTINGS TABLE (FOR COMPLIANCE)
-- Stores legal business info for receipts, VAT status, currency, etc.
-- =======================================

CREATE TABLE business_settings (
    id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    business_name VARCHAR(255) NOT NULL,
    business_address TEXT NOT NULL,
    business_phone VARCHAR(20),
    business_email VARCHAR(255),
    vat_registration_number VARCHAR(20),
    is_vat_registered BOOLEAN NOT NULL DEFAULT FALSE,
    default_vat_rate NUMERIC(5,2) NOT NULL DEFAULT 20.00,
    currency_code CHAR(3) NOT NULL DEFAULT 'GBP',
    website_url TEXT,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Add trigger to auto-update timestamp
CREATE OR REPLACE FUNCTION update_business_settings_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_update_business_settings_timestamp
    BEFORE UPDATE ON business_settings
    FOR EACH ROW
    EXECUTE FUNCTION update_business_settings_timestamp();

CREATE TRIGGER trigger_update_discount_campaigns_timestamp
    BEFORE UPDATE ON discount_campaigns
    FOR EACH ROW
    EXECUTE FUNCTION update_business_settings_timestamp();

-- Insert default row
INSERT INTO business_settings (
    business_name,
    business_address,
    business_phone,
    business_email,
    vat_registration_number,
    is_vat_registered,
    default_vat_rate,
    currency_code,
    website_url
) VALUES (
    'Crussell Nail Art Studio',
    'Address',
    '+44 131 123 4567',
    'crussellnails@gmail.com',
    NULL,  -- Set this when we register for VAT
    FALSE, -- Set to TRUE when we register for VAT
    20.00,
    'GBP',
    'https://www.website.co.uk'
);

CREATE TYPE admin_notification_reason AS ENUM ('pending_booking', 'cancelled_booking', 'rescheduled_booking', '1_week_no_pay', '1_month_no_pay', 'affiliate_claim', 'late_cancellation', 'no_deposit', 'deposit_paid', 'edit_request', 'new_booking', 'edit_requested');

CREATE TABLE admin_notifications (
    id SERIAL PRIMARY KEY,
    reason admin_notification_reason NOT NULL,
    booking_id CHAR(12) REFERENCES bookings(id),
    user_id CHAR(12) REFERENCES users(id),
    acknowledged_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- User notification preferences for future user notification system
CREATE TABLE user_notification_preferences (
    id SERIAL PRIMARY KEY,
    user_id CHAR(12) REFERENCES users(id) ON DELETE CASCADE,
    email_enabled BOOLEAN DEFAULT false,
    sms_enabled BOOLEAN DEFAULT false,
    browser_push_enabled BOOLEAN DEFAULT false,
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_user_notification_preferences_user_id ON user_notification_preferences(user_id);

CREATE INDEX idx_payments_booking_id_status ON payments(booking_id, status);
CREATE INDEX idx_bookings_start_time_status ON bookings(start_time, status);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_payments_created_at_status ON payments(created_at, status);

create table images (
    id uuid primary key default gen_random_uuid(),
    url text not null,
    thumbnail_url text not null,
    tag_names text[] not null default '{}',
    created_at timestamptz not null default now()
);

-- only for autocomplete text
create table tags (
    id serial primary key,
    name text not null unique
);

-- Indexes
create index idx_images_tag_names on images using gin(tag_names);
-- create index idx_images_tag_names_trgm on images using gin ((tag_names::text[]) gin_trgm_ops);
create index idx_images_created_at on images(created_at desc);
create index idx_tags_name_trgm on tags using gin (name gin_trgm_ops);

-- =======================================
-- GDPR COMPLIANCE FUNCTIONS
-- =======================================

/*
GDPR COMPLIANCE NOTES:
- Legal basis for booking data: Contract performance (Art 6(1)(b) GDPR)
- Legal basis for optional data (marketing): Consent (Art 6(1)(a) GDPR)
- Data retention: While consent remains and account is active
- Right to be forgotten: anonymize_user() for registered users
- Subject access rights: export_all_user_data() provides complete export
*/

-- Anonymize registered user (Right to be Forgotten)
-- WHY: GDPR Article 17 - users can request data deletion
-- WHEN: User requests account deletion
-- OUTPUT: Converts personal data to anonymous placeholder
-- NOTE: phone/date_of_birth are NOT NULL so we use placeholder values, not NULL
CREATE OR REPLACE FUNCTION anonymize_user(target_id CHAR(12))
RETURNS VOID AS $$
BEGIN
    UPDATE users
    SET
        n_first_name          = 'Deleted',
        n_last_name           = 'User',
        email                 = CONCAT('deleted+', target_id, '@deleted.invalid'),
        phone                 = '+000000000000',          -- NOT NULL column: use placeholder
        date_of_birth         = '1900-01-01',             -- NOT NULL column: use placeholder
        profile_pic_url       = NULL,
        account_role          = 'guest',
        loyalty_stamps        = 0,
        referral_code         = NULL,
        data_retention_consent = FALSE,
        data_consent_updated_at = NOW(),
        updated_at            = NOW(),
        password_hash         = NULL
    WHERE id = target_id
      AND account_role != 'guest';
END;
$$ LANGUAGE plpgsql;

-- Delete guest user completely (no contractual retention basis)
-- WHY: Guest accounts have no ongoing contractual or legal basis for retention
-- WHEN: User requests deletion or GDPR cleanup
-- OUTPUT: Full removal of guest account from users table
CREATE OR REPLACE FUNCTION delete_guest_user(target_id CHAR(12))
RETURNS VOID AS $$
BEGIN
    DELETE FROM users WHERE id = target_id AND account_role = 'guest';
END;
$$ LANGUAGE plpgsql;

-- Update consent
-- WHY: GDPR requires tracking consent changes
-- WHEN: User updates privacy preferences
-- OUTPUT: Updates consent flags and timestamps
CREATE OR REPLACE FUNCTION update_data_consent(target_id CHAR(12), consent BOOLEAN)
RETURNS VOID AS $$
BEGIN
    UPDATE users
    SET data_retention_consent  = consent,
        data_consent_updated_at = NOW(),
        updated_at              = NOW()
    WHERE id = target_id;
END;
$$ LANGUAGE plpgsql;

-- Complete Subject Access Request Export
-- WHY: GDPR Article 15 - users have right to access their data
-- WHEN: Customer requests "what data do you have on me?"
-- OUTPUT: Complete JSON export of all user data
-- USE: Call from Go API endpoint, return to user via email/download
CREATE OR REPLACE FUNCTION export_all_user_data(target_user_id CHAR(12))
RETURNS JSON AS $$
DECLARE
    result JSON;
BEGIN
    SELECT json_build_object(
        'user_profile', (
            SELECT json_build_object(
                'id',                                id,
                'first_name',                        n_first_name,
                'last_name',                         n_last_name,
                'full_name',                         fn,
                'email',                             email,
                'phone',                             phone,
                'date_of_birth',                     date_of_birth,
                'profile_pic_url',                   profile_pic_url,
                'account_role',                      account_role,
                'account_type',                      account_type,
                'last_login_at',                     last_login_at,
                'loyalty_stamps',                    loyalty_stamps,
                'deposits_required',                 deposits_required,
                'referral_code',                     referral_code,
                'privacy_policy_and_terms_consent',  privacy_policy_and_terms_consent,
                'policy_consent_updated_at',         policy_consent_updated_at,
                'data_retention_consent',            data_retention_consent,
                'data_consent_updated_at',           data_consent_updated_at,
                'created_at',                        created_at,
                'updated_at',                        updated_at
            )
            FROM users WHERE id = target_user_id
        ),
        'bookings', (
            SELECT COALESCE(json_agg(
                json_build_object(
                    'booking_id',  b.id,
                    'start_time',  b.start_time,
                    'status',      b.status,
                    'notes',       b.notes,
                    'created_by',  b.created_by,
                    'created_at',  b.created_at,
                    'updated_at',  b.updated_at,
                    'services', (
                        SELECT COALESCE(json_agg(
                            json_build_object(
                                'service_id',        s.id,
                                'name',              s.name,
                                'description',       s.description,
                                'price',             COALESCE(bsvc.override_price, s.price),
                                'duration_minutes',  COALESCE(bsvc.override_duration_minutes, s.duration_minutes)
                            )
                        ), '[]'::json)
                        FROM booking_services bsvc
                        JOIN services s ON bsvc.service_id = s.id
                        WHERE bsvc.booking_id = b.id
                    )
                )
            ORDER BY b.start_time DESC), '[]'::json)
            FROM bookings b WHERE b.user_id = target_user_id
        ),
        'payments', (
            SELECT COALESCE(json_agg(
                json_build_object(
                    'payment_id',     p.id,
                    'booking_id',     p.booking_id,
                    'payment_type',   p.payment_type,
                    'payment_method', p.payment_method,
                    'vendor_code',    p.vendor_code,
                    'invoice_number', p.invoice_number,
                    'status',         p.status,
                    'amount',         p.amount,
                    'vat_amount',     p.vat_amount,
                    'net_amount',     p.net_amount,
                    'created_at',     p.created_at,
                    'updated_at',     p.updated_at
                )
            ORDER BY p.created_at DESC), '[]'::json)
            FROM payments p
            JOIN bookings b ON p.booking_id = b.id
            WHERE b.user_id = target_user_id
        ),
        'patch_tests', (
            SELECT COALESCE(json_agg(
                json_build_object(
                    'patch_test_id',   upt.patch_test_id,
                    'name',            pt.name,
                    'description',     pt.description,
                    'tested_at',       upt.tested_at
                )
            ORDER BY upt.tested_at DESC), '[]'::json)
            FROM user_patch_tests upt
            JOIN patch_tests pt ON upt.patch_test_id = pt.id
            WHERE upt.user_id = target_user_id
        ),
        'referrals', (
            SELECT json_build_object(
                'referred_by', (
                    -- The user who referred this user (if any)
                    -- First name only: referrer's name is their own personal data,
                    -- included here only to make the SAR meaningful to the recipient.
                    SELECT json_build_object(
                        'referrer_id',         ur.referrer_id,
                        'referrer_first_name', u.n_first_name,
                        'referred_at',         ur.referred_at,
                        'claimed_booking_id',  ur.claimed_booking_id
                    )
                    FROM user_referrals ur
                    JOIN users u ON u.id = ur.referrer_id
                    WHERE ur.referred_id = target_user_id
                    LIMIT 1
                ),
                'referred_users', (
                    -- Users this person has referred
                    -- First name only: same reasoning as above.
                    SELECT COALESCE(json_agg(
                        json_build_object(
                            'referred_id',         ur.referred_id,
                            'referred_first_name', u.n_first_name,
                            'referred_at',         ur.referred_at,
                            'claimed_booking_id',  ur.claimed_booking_id
                        )
                    ORDER BY ur.referred_at DESC), '[]'::json)
                    FROM user_referrals ur
                    JOIN users u ON u.id = ur.referred_id
                    WHERE ur.referrer_id = target_user_id
                )
            )
        ),
        'notification_preferences', (
            SELECT COALESCE(json_agg(
                json_build_object(
                    'email_enabled',        unp.email_enabled,
                    'sms_enabled',          unp.sms_enabled,
                    'browser_push_enabled', unp.browser_push_enabled,
                    'updated_at',           unp.updated_at
                )
            ), '[]'::json)
            FROM user_notification_preferences unp
            WHERE unp.user_id = target_user_id
        ),
        'export_metadata', json_build_object(
            'exported_at',    NOW(),
            'exported_by',    'system',
            'user_id',        target_user_id,
            'format_version', '1.1'
        )
    ) INTO result;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

-- =======================================
-- TAX COMPLIANCE FUNCTIONS
-- =======================================

/*
TAX COMPLIANCE NOTES:
- Below £90k turnover: No VAT registration required
- Above £90k turnover: VAT registration mandatory, Making Tax Digital required
- All businesses: Income tax records required (simplified from April 2026/2027)
- VAT rate: 20% standard rate applies to nail services
*/

-- VAT Return Data (for Making Tax Digital submission)
-- WHY: VAT registered businesses must submit quarterly VAT returns via MTD software
-- WHEN: Every quarter if VAT registered (£90k+ annual turnover)
-- OUTPUT: Summary totals for VAT return - total sales, VAT charged, net sales
-- USE: Export to QuickBooks/Xero for MTD submission to HMRC
CREATE OR REPLACE FUNCTION get_vat_return_data(
    start_date DATE,
    end_date   DATE
)
RETURNS TABLE (
    period_start       DATE,
    period_end         DATE,
    total_sales        NUMERIC(12,2),
    total_vat_charged  NUMERIC(12,2),
    net_sales          NUMERIC(12,2),
    transaction_count  BIGINT
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        start_date AS period_start,
        end_date   AS period_end,
        COALESCE(SUM(p.amount), 0) AS total_sales,
        COALESCE(SUM(
            CASE
                WHEN p.vat_amount IS NOT NULL THEN p.vat_amount
                WHEN (SELECT is_vat_registered FROM business_settings WHERE id = 1) THEN
                    ROUND(p.amount - (p.amount / (1 + COALESCE(p.vat_rate,
                        (SELECT default_vat_rate FROM business_settings WHERE id = 1)) / 100)), 2)
                ELSE 0
            END
        ), 0) AS total_vat_charged,
        COALESCE(SUM(
            CASE
                WHEN p.net_amount IS NOT NULL THEN p.net_amount
                WHEN (SELECT is_vat_registered FROM business_settings WHERE id = 1) THEN
                    ROUND(p.amount / (1 + COALESCE(p.vat_rate,
                        (SELECT default_vat_rate FROM business_settings WHERE id = 1)) / 100), 2)
                ELSE p.amount
            END
        ), 0) AS net_sales,
        COUNT(*) AS transaction_count
    FROM payments p
    JOIN bookings b ON p.booking_id = b.id
    WHERE p.status = 'completed'
      AND p.created_at::date BETWEEN start_date AND end_date
      AND p.payment_type IN ('full', 'partial', 'balance');
END;
$$ LANGUAGE plpgsql;

-- Detailed Transaction Export (Primary tax function)
-- WHY: All businesses need transaction records for tax returns and accounting
-- WHEN: Monthly/quarterly for accounting software import
-- OUTPUT: CSV-compatible transaction list with customer, service, payment details
-- USE: Import directly into QuickBooks, Xero, or other accounting software
-- NOTE: include_vat=false (default for micro businesses); set true only when VAT registered
CREATE OR REPLACE FUNCTION export_sales_transactions(
    start_date  DATE,
    end_date    DATE,
    include_vat BOOLEAN DEFAULT FALSE
)
RETURNS TABLE (
    transaction_date  DATE,
    invoice_number    TEXT,
    customer_name     TEXT,
    customer_email    TEXT,
    service_description TEXT,
    payment_method    TEXT,
    gross_amount      NUMERIC(10,2),
    net_amount        NUMERIC(10,2),
    vat_amount        NUMERIC(10,2),
    vat_rate          NUMERIC(5,2),
    booking_id        CHAR(12),
    payment_id        CHAR(12)
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        p.created_at::date AS transaction_date,
        p.invoice_number::text AS invoice_number,
        COALESCE(
            CASE WHEN u.n_first_name = 'Deleted' THEN NULL ELSE u.fn END,
            'Walk-in Customer'
        ) AS customer_name,
        CASE WHEN u.n_first_name = 'Deleted' THEN NULL ELSE u.email END AS customer_email,
        string_agg(s.name, ', ' ORDER BY s.name) AS service_description,
        p.payment_method::text AS payment_method,
        p.amount AS gross_amount,
        CASE
            WHEN include_vat AND p.net_amount IS NOT NULL THEN p.net_amount
            WHEN include_vat THEN ROUND(p.amount / 1.20, 2)
            ELSE p.amount
        END AS net_amount,
        CASE
            WHEN include_vat AND p.vat_amount IS NOT NULL THEN p.vat_amount
            WHEN include_vat THEN ROUND(p.amount - (p.amount / 1.20), 2)
            ELSE 0
        END AS vat_amount,
        CASE
            WHEN include_vat THEN COALESCE(p.vat_rate, 20.00)
            ELSE NULL
        END AS vat_rate,
        b.id AS booking_id,
        p.id AS payment_id
    FROM payments p
    JOIN bookings b ON p.booking_id = b.id
    LEFT JOIN users u ON b.user_id = u.id
    LEFT JOIN booking_services bsvc ON b.id = bsvc.booking_id
    LEFT JOIN services s ON bsvc.service_id = s.id
    WHERE p.status = 'completed'
      AND p.created_at::date BETWEEN start_date AND end_date
      AND p.payment_type IN ('full', 'partial', 'balance')
    GROUP BY p.id, b.id, u.fn, u.n_first_name, u.email,
             p.created_at, p.amount, p.net_amount, p.vat_amount,
             p.vat_rate, p.payment_method, p.invoice_number
    ORDER BY p.created_at;
END;
$$ LANGUAGE plpgsql;

-- Monthly Business Summary
-- WHY: Track business performance trends and payment method preferences
-- WHEN: Monthly review of business performance
-- OUTPUT: Month-by-month breakdown of revenue, bookings, payment methods
-- USE: Personal business insights - "How am I doing vs last month?"
CREATE OR REPLACE FUNCTION get_monthly_business_summary(
    start_date DATE,
    end_date   DATE
)
RETURNS TABLE (
    month_year        TEXT,
    total_bookings    BIGINT,
    total_revenue     NUMERIC(12,2),
    cash_payments     NUMERIC(12,2),
    card_payments     NUMERIC(12,2),
    online_payments   NUMERIC(12,2),
    avg_transaction   NUMERIC(10,2)
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        TO_CHAR(p.created_at, 'YYYY-MM') AS month_year,
        COUNT(DISTINCT b.id) AS total_bookings,
        SUM(p.amount) AS total_revenue,
        SUM(CASE WHEN p.payment_method = 'cash'          THEN p.amount ELSE 0 END) AS cash_payments,
        SUM(CASE WHEN p.payment_method = 'in_person_card' THEN p.amount ELSE 0 END) AS card_payments,
        SUM(CASE WHEN p.payment_method = 'online_square'  THEN p.amount ELSE 0 END) AS online_payments,
        ROUND(AVG(p.amount), 2) AS avg_transaction
    FROM payments p
    JOIN bookings b ON p.booking_id = b.id
    WHERE p.status = 'completed'
      AND p.created_at::date BETWEEN start_date AND end_date
      AND p.payment_type IN ('full', 'partial', 'balance')
    GROUP BY TO_CHAR(p.created_at, 'YYYY-MM')
    ORDER BY month_year;
END;
$$ LANGUAGE plpgsql;

-- Simple Sales Totals (Quick business snapshot)
-- WHY: Quick overview of current performance for dashboard/API
-- WHEN: Dashboard loading, daily/weekly check-ins
-- OUTPUT: Total sales, transaction count, payment method breakdown
-- USE: Homepage dashboard, "How much did I make this week?" queries
CREATE OR REPLACE FUNCTION get_sales_totals(
    start_date DATE,
    end_date   DATE
)
RETURNS TABLE (
    total_sales        NUMERIC(12,2),
    total_transactions BIGINT,
    cash_total         NUMERIC(12,2),
    card_total         NUMERIC(12,2),
    online_total       NUMERIC(12,2)
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        COALESCE(SUM(p.amount), 0) AS total_sales,
        COUNT(*) AS total_transactions,
        COALESCE(SUM(CASE WHEN p.payment_method = 'cash'           THEN p.amount ELSE 0 END), 0) AS cash_total,
        COALESCE(SUM(CASE WHEN p.payment_method = 'in_person_card'  THEN p.amount ELSE 0 END), 0) AS card_total,
        COALESCE(SUM(CASE WHEN p.payment_method = 'online_square'   THEN p.amount ELSE 0 END), 0) AS online_total
    FROM payments p
    JOIN bookings b ON p.booking_id = b.id
    WHERE p.status = 'completed'
      AND p.created_at::date BETWEEN start_date AND end_date
      AND p.payment_type IN ('full', 'partial', 'balance');
END;
$$ LANGUAGE plpgsql;

-- =======================================
-- UTILITY FUNCTIONS
-- =======================================

-- VAT Registration Transition Function
-- WHY: When crossing £90k threshold, must register for VAT and backfill existing data
-- WHEN: One-time use when VAT registration becomes mandatory
-- OUTPUT: Updates all historical payments with VAT breakdown + sets new defaults
-- USE: Call once when we register for VAT - transforms business from non-VAT to VAT
CREATE OR REPLACE FUNCTION enable_vat_registration(
    registration_date DATE    DEFAULT CURRENT_DATE,
    vat_rate          NUMERIC(5,2) DEFAULT 20.00,
    vat_reg_number    TEXT    DEFAULT NULL
)
RETURNS TABLE (
    payments_updated           INT,
    total_vat_calculated       NUMERIC(12,2),
    total_net_calculated       NUMERIC(12,2),
    registration_effective_date DATE,
    settings_updated           BOOLEAN
) AS $$
DECLARE
    updated_count INT;
    total_vat     NUMERIC(12,2);
    total_net     NUMERIC(12,2);
    settings_ok   BOOLEAN := FALSE;
BEGIN
    UPDATE payments
    SET
        vat_rate        = enable_vat_registration.vat_rate,
        vat_amount      = ROUND(amount - (amount / (1 + enable_vat_registration.vat_rate / 100)), 2),
        net_amount      = ROUND(amount / (1 + enable_vat_registration.vat_rate / 100), 2),
        is_vat_applicable = TRUE,
        updated_at      = NOW()
    WHERE status = 'completed'
      AND created_at::date >= registration_date
      AND vat_amount IS NULL;

    GET DIAGNOSTICS updated_count = ROW_COUNT;

    SELECT
        COALESCE(SUM(p.vat_amount), 0),
        COALESCE(SUM(p.net_amount), 0)
    INTO total_vat, total_net
    FROM payments p
    WHERE status = 'completed'
      AND created_at::date >= registration_date
      AND vat_amount IS NOT NULL;

    UPDATE business_settings
    SET
        is_vat_registered       = TRUE,
        vat_registration_number = COALESCE(vat_reg_number, vat_registration_number),
        default_vat_rate        = enable_vat_registration.vat_rate,
        updated_at              = NOW()
    WHERE id = 1;

    IF FOUND THEN
        settings_ok := TRUE;
    END IF;

    RETURN QUERY SELECT
        updated_count,
        total_vat,
        total_net,
        registration_date,
        settings_ok;
END;
$$ LANGUAGE plpgsql;

-- Update payment with VAT (for new payments after VAT registration)
-- WHY: After VAT registration, all new payments need VAT calculation
-- WHEN: Called when processing new payments (if VAT registered)
-- OUTPUT: Updates individual payment with VAT breakdown
-- USE: Call from Go when processing new payments after VAT registration
CREATE OR REPLACE FUNCTION apply_vat_to_payment(
    payment_id CHAR(12),
    vat_rate   NUMERIC(5,2) DEFAULT NULL  -- NULL = use default from business_settings
)
RETURNS VOID AS $$
DECLARE
    effective_rate NUMERIC(5,2);
BEGIN
    IF vat_rate IS NULL THEN
        SELECT default_vat_rate INTO effective_rate
        FROM business_settings
        WHERE id = 1 AND is_vat_registered = TRUE;

        IF NOT FOUND OR effective_rate IS NULL THEN
            RAISE EXCEPTION 'No VAT rate provided and business is not registered for VAT';
        END IF;
    ELSE
        effective_rate := vat_rate;
    END IF;

    UPDATE payments
    SET
        vat_rate          = effective_rate,
        vat_amount        = ROUND(amount - (amount / (1 + effective_rate / 100)), 2),
        net_amount        = ROUND(amount / (1 + effective_rate / 100), 2),
        is_vat_applicable = TRUE,
        updated_at        = NOW()
    WHERE id = payment_id
      AND vat_amount IS NULL;
END;
$$ LANGUAGE plpgsql;

-- Simple VAT Calculation Helper
-- WHY: Manual VAT calculations or validation
-- WHEN: Checking VAT calculations or manual entry corrections
-- OUTPUT: net amount and vat amount from gross amount
-- USE: Manual calculations, validation, or corrections
CREATE OR REPLACE FUNCTION calculate_vat(
    gross_amount NUMERIC(10,2),
    vat_rate     NUMERIC(5,2) DEFAULT NULL  -- NULL = use default from business_settings
)
RETURNS TABLE(
    net NUMERIC(10,2),
    vat NUMERIC(10,2)
) AS $$
DECLARE
    effective_rate NUMERIC(5,2);
BEGIN
    IF vat_rate IS NULL THEN
        SELECT default_vat_rate INTO effective_rate
        FROM business_settings
        WHERE id = 1 AND is_vat_registered = TRUE;

        IF NOT FOUND OR effective_rate IS NULL THEN
            RAISE EXCEPTION 'No VAT rate provided and business is not registered for VAT';
        END IF;
    ELSE
        effective_rate := vat_rate;
    END IF;

    RETURN QUERY
    SELECT
        ROUND(gross_amount / (1 + effective_rate / 100), 2) AS net,
        gross_amount - ROUND(gross_amount / (1 + effective_rate / 100), 2) AS vat;
END;
$$ LANGUAGE plpgsql;

-- =======================================
-- UK RECEIPT COMPLIANCE FUNCTION
-- =======================================

/*
UK RECEIPT REQUIREMENTS:
- Business name and address
- Date and time of transaction
- Description of goods/services
- Amount charged (including VAT if registered)
- VAT breakdown (if VAT registered): VAT number, rate, VAT amount, net amount
- Receipt/invoice number
- Payment method
- Customer details (if not anonymised)
*/

-- Complete Receipt Data for UK Compliance
-- WHY: Generate legally compliant receipts for customers
-- WHEN: After each completed payment/booking
-- OUTPUT: All data needed for receipt printing/email
-- USE: Call from Go API to generate customer receipts
CREATE OR REPLACE FUNCTION get_receipt_data(p_payment_id CHAR(12))
RETURNS TABLE (
    -- Business Information
    business_name           TEXT,
    business_address        TEXT,
    business_phone          TEXT,
    business_email          TEXT,
    vat_registration_number TEXT,
    is_vat_registered       BOOLEAN,

    -- Payment Information
    payment_id              CHAR(12),
    invoice_number          TEXT,
    transaction_date        TIMESTAMPTZ,
    payment_method          TEXT,
    payment_status          TEXT,

    -- Customer Information
    customer_id             CHAR(12),
    customer_name           TEXT,
    customer_email          TEXT,
    customer_phone          TEXT,

    -- Booking Information
    booking_id              CHAR(12),
    appointment_date        TIMESTAMPTZ,
    booking_status          TEXT,

    -- Service Details
    services                JSON,
    total_duration_minutes  INT,

    -- Financial Information
    gross_amount            NUMERIC(10,2),
    net_amount              NUMERIC(10,2),
    vat_amount              NUMERIC(10,2),
    vat_rate                NUMERIC(5,2),
    is_vat_applicable       BOOLEAN,

    -- Receipt Metadata
    receipt_generated_at    TIMESTAMPTZ,
    currency_code           TEXT
) AS $$
DECLARE
    bs RECORD;
BEGIN
    SELECT INTO bs
        bs2.business_name,
        bs2.business_address,
        bs2.business_phone,
        bs2.business_email,
        bs2.vat_registration_number,
        bs2.is_vat_registered,
        bs2.currency_code
    FROM business_settings bs2
    WHERE bs2.id = 1;

    RETURN QUERY
    SELECT
        bs.business_name,
        bs.business_address,
        bs.business_phone,
        bs.business_email,
        bs.vat_registration_number,
        bs.is_vat_registered,

        p.id AS payment_id,
        COALESCE('INV-' || p.invoice_number::text, 'INV-' || p.id) AS invoice_number,
        p.created_at AS transaction_date,
        p.payment_method::text AS payment_method,
        p.status::text AS payment_status,

        u.id AS customer_id,
        CASE
            WHEN u.id IS NULL               THEN 'Walk-in Customer'
            WHEN u.n_first_name = 'Deleted' THEN 'Walk-in Customer'
            WHEN u.account_role = 'guest'   THEN 'Walk-in Customer'
            ELSE u.fn
        END AS customer_name,
        CASE
            WHEN u.id IS NULL               THEN NULL
            WHEN u.n_first_name = 'Deleted' THEN NULL
            WHEN u.account_role = 'guest'   THEN NULL
            ELSE u.email
        END AS customer_email,
        CASE
            WHEN u.id IS NULL               THEN NULL
            WHEN u.n_first_name = 'Deleted' THEN NULL
            WHEN u.account_role = 'guest'   THEN NULL
            ELSE u.phone
        END AS customer_phone,

        b.id AS booking_id,
        b.start_time AS appointment_date,
        b.status::text AS booking_status,

        COALESCE((
            SELECT json_agg(
                json_build_object(
                    'service_id',       s.id,
                    'name',             s.name,
                    'description',      s.description,
                    'price',            COALESCE(bsvc.override_price, s.price),
                    'duration_minutes', COALESCE(bsvc.override_duration_minutes, s.duration_minutes)
                )
                ORDER BY s.name
            )
            FROM booking_services bsvc
            JOIN services s ON bsvc.service_id = s.id
            WHERE bsvc.booking_id = b.id
        ), '[]'::json) AS services,

        COALESCE((
            SELECT SUM(COALESCE(bsvc.override_duration_minutes, s.duration_minutes))
            FROM booking_services bsvc
            JOIN services s ON bsvc.service_id = s.id
            WHERE bsvc.booking_id = b.id
        ), 0)::INT AS total_duration_minutes,

        p.amount AS gross_amount,
        COALESCE(p.net_amount, p.amount) AS net_amount,
        COALESCE(p.vat_amount, 0.00) AS vat_amount,
        p.vat_rate AS vat_rate,
        p.is_vat_applicable AS is_vat_applicable,

        NOW() AS receipt_generated_at,
        bs.currency_code AS currency_code

    FROM payments p
    JOIN bookings b ON p.booking_id = b.id
    LEFT JOIN users u ON b.user_id = u.id
    WHERE p.id = p_payment_id;
END;
$$ LANGUAGE plpgsql;

-- =======================================
-- FUNCTION USAGE SUMMARY
-- =======================================
/*
This summary organises all database functions by purpose, legal basis, and expected
usage frequency — providing a clear guide for application integration.

--------------------------------------------------------------------------------
1. FINANCIAL & TAX COMPLIANCE
--------------------------------------------------------------------------------

REGULAR USE (called from API endpoints):
- get_sales_totals(start_date, end_date)
  → Dashboard metrics: total revenue, transaction count, payment method breakdown.
- get_receipt_data(payment_id)
  → UK-compliant receipt after every completed payment.
- calculate_vat(gross_amount, vat_rate)
  → Validate or manually compute VAT splits (only usable when VAT registered).

PERIODIC USE (monthly/quarterly for accounting or HMRC):
- export_sales_transactions(start_date, end_date, include_vat)
  → Primary export for QuickBooks / Xero. Set include_vat=true only if VAT registered.
- get_monthly_business_summary(start_date, end_date)
  → Revenue trends, avg. transaction, and payment method adoption month by month.
- get_vat_return_data(start_date, end_date)
  → MTD-ready VAT return summary for HMRC (only relevant when VAT registered).

ONE-TIME / TRANSITIONAL:
- enable_vat_registration(registration_date, vat_rate, vat_reg_number)
  → Run once on crossing the £90k VAT threshold; backfills historical payments.
- apply_vat_to_payment(payment_id, vat_rate)
  → Called for every new payment after VAT registration.

--------------------------------------------------------------------------------
2. GDPR & DATA PRIVACY
--------------------------------------------------------------------------------

ON-DEMAND (triggered by user request or admin action):
- export_all_user_data(user_id)
  → Full Subject Access Request (SAR) export in JSON — GDPR Article 15.
  → Includes profile, bookings (with service overrides), payments, patch tests.
- anonymize_user(user_id)
  → Right to Erasure for registered users — GDPR Article 17.
  → Preserves booking/payment audit trail; replaces PII with placeholders.
  → NOTE: phone and date_of_birth use placeholder values (NOT NULL columns).
- delete_guest_user(user_id)
  → Complete deletion of guest accounts (no contractual retention basis).
- update_data_consent(user_id, consent)
  → Records updated consent with timestamp for auditability.

NOTE: Booking/payment data is retained under contractual necessity (GDPR Art. 6(1)(b))
even after consent withdrawal. Only optional/marketing data is consent-governed.

--------------------------------------------------------------------------------
3. INTEGRATION QUICK REFERENCE
--------------------------------------------------------------------------------
• User deletes account  → anonymize_user()       or delete_guest_user()
• "What data do you have?" → export_all_user_data()
• New payment           → apply_vat_to_payment() (if VAT reg) + get_receipt_data()
• Monthly close         → export_sales_transactions(..., include_vat := [true/false])
• VAT registration day  → enable_vat_registration() once, then apply_vat_to_payment()
• Dashboard load        → get_sales_totals() + get_monthly_business_summary()
*/
