-- ============================================================================
-- ERP GENERIC - DATABASE PREREQUISITES
-- ============================================================================
-- Version: 1.0.0
-- Description: Extensions, common types, and utility functions
-- Execute: FIRST (before any schema)
-- ============================================================================

-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";      -- UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto";       -- Password hashing
CREATE EXTENSION IF NOT EXISTS "pg_trgm";        -- Trigram similarity (fuzzy search)
CREATE EXTENSION IF NOT EXISTS "unaccent";       -- Remove accents for search

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

-- Function: Update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION update_updated_at_column() IS
'Generic trigger function to auto-update updated_at timestamp on row modification';

-- Function: Normalize text for search (remove accents, lowercase)
CREATE OR REPLACE FUNCTION normalize_search_text(p_text TEXT)
RETURNS TEXT AS $$
BEGIN
    RETURN LOWER(unaccent(COALESCE(p_text, '')));
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION normalize_search_text(TEXT) IS
'Normalize text for search by removing accents and converting to lowercase';

-- Function: Generate random alphanumeric code
CREATE OR REPLACE FUNCTION generate_random_code(p_length INTEGER DEFAULT 8)
RETURNS TEXT AS $$
DECLARE
    chars TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    result TEXT := '';
    i INTEGER;
BEGIN
    FOR i IN 1..p_length LOOP
        result := result || substr(chars, floor(random() * length(chars) + 1)::INTEGER, 1);
    END LOOP;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION generate_random_code(INTEGER) IS
'Generate random alphanumeric code of specified length (default 8)';

-- Function: Validate email format
CREATE OR REPLACE FUNCTION is_valid_email(p_email TEXT)
RETURNS BOOLEAN AS $$
BEGIN
    RETURN p_email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION is_valid_email(TEXT) IS
'Validate email format using regex';

-- Function: Validate phone number format (basic)
CREATE OR REPLACE FUNCTION is_valid_phone(p_phone TEXT)
RETURNS BOOLEAN AS $$
BEGIN
    -- Basic validation: only digits, spaces, dashes, parentheses, plus sign
    RETURN p_phone ~ '^[\d\s\-\(\)\+]+$' AND length(regexp_replace(p_phone, '[^\d]', '', 'g')) >= 7;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION is_valid_phone(TEXT) IS
'Validate phone number format (at least 7 digits)';

-- Function: Clean phone number (keep only digits)
CREATE OR REPLACE FUNCTION clean_phone(p_phone TEXT)
RETURNS TEXT AS $$
BEGIN
    RETURN regexp_replace(COALESCE(p_phone, ''), '[^\d]', '', 'g');
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION clean_phone(TEXT) IS
'Remove non-numeric characters from phone number';

-- Function: Calculate age from date
CREATE OR REPLACE FUNCTION calculate_age(p_birthdate DATE)
RETURNS INTEGER AS $$
BEGIN
    IF p_birthdate IS NULL THEN
        RETURN NULL;
    END IF;
    RETURN EXTRACT(YEAR FROM age(CURRENT_DATE, p_birthdate))::INTEGER;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION calculate_age(DATE) IS
'Calculate age in years from birthdate';

-- Function: Get current fiscal year start
CREATE OR REPLACE FUNCTION get_fiscal_year_start(p_date DATE DEFAULT CURRENT_DATE)
RETURNS DATE AS $$
BEGIN
    -- Assuming fiscal year starts January 1st
    -- Modify if different fiscal year start is needed
    RETURN DATE_TRUNC('year', p_date)::DATE;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION get_fiscal_year_start(DATE) IS
'Get the start date of fiscal year for a given date (default: January 1st)';

-- Function: Round to decimal places
CREATE OR REPLACE FUNCTION round_currency(p_amount NUMERIC, p_decimals INTEGER DEFAULT 2)
RETURNS NUMERIC AS $$
BEGIN
    RETURN ROUND(COALESCE(p_amount, 0), p_decimals);
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION round_currency(NUMERIC, INTEGER) IS
'Round numeric value to specified decimal places (default 2 for currency)';

-- ============================================================================
-- COMMON TYPES
-- ============================================================================

-- Type: Money with currency (for multi-currency support)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'money_amount') THEN
        CREATE TYPE money_amount AS (
            amount NUMERIC(15, 2),
            currency_code CHAR(3)
        );
    END IF;
END $$;

COMMENT ON TYPE money_amount IS
'Composite type for storing monetary values with currency code';

-- Type: Address components
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'address_components') THEN
        CREATE TYPE address_components AS (
            street VARCHAR(255),
            street2 VARCHAR(255),
            city VARCHAR(100),
            state VARCHAR(100),
            zip VARCHAR(20),
            country_code CHAR(2)
        );
    END IF;
END $$;

COMMENT ON TYPE address_components IS
'Composite type for address components (street, city, state, zip, country)';

-- ============================================================================
-- SCHEMA CREATION
-- ============================================================================

-- Create all schemas upfront to avoid circular dependency issues
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS core;
CREATE SCHEMA IF NOT EXISTS analytics;
CREATE SCHEMA IF NOT EXISTS financial;
CREATE SCHEMA IF NOT EXISTS inventory;
CREATE SCHEMA IF NOT EXISTS purchase;
CREATE SCHEMA IF NOT EXISTS sales;
CREATE SCHEMA IF NOT EXISTS projects;
CREATE SCHEMA IF NOT EXISTS system;

-- Set search path to include all schemas
ALTER DATABASE erp_generic SET search_path TO public, auth, core, analytics, financial, inventory, purchase, sales, projects, system;

-- Grant usage on schemas to public role (will be refined per-user later)
GRANT USAGE ON SCHEMA auth TO PUBLIC;
GRANT USAGE ON SCHEMA core TO PUBLIC;
GRANT USAGE ON SCHEMA analytics TO PUBLIC;
GRANT USAGE ON SCHEMA financial TO PUBLIC;
GRANT USAGE ON SCHEMA inventory TO PUBLIC;
GRANT USAGE ON SCHEMA purchase TO PUBLIC;
GRANT USAGE ON SCHEMA sales TO PUBLIC;
GRANT USAGE ON SCHEMA projects TO PUBLIC;
GRANT USAGE ON SCHEMA system TO PUBLIC;

-- ============================================================================
-- PREREQUISITES COMPLETE
-- ============================================================================

DO $$
BEGIN
    RAISE NOTICE 'Prerequisites installed successfully!';
    RAISE NOTICE 'Extensions: uuid-ossp, pgcrypto, pg_trgm, unaccent';
    RAISE NOTICE 'Schemas created: auth, core, analytics, financial, inventory, purchase, sales, projects, system';
    RAISE NOTICE 'Utility functions: 9 functions installed';
END $$;
