-- ============================================================================
-- CRM Database Schema — Enterprise RBAC + CRM
-- PostgreSQL 15+ | UUID PKs | 3NF | Hierarchical RBAC | Full Audit Trail
-- ============================================================================

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- ============================================================================
-- HELPER: update updated_at automatically
-- ============================================================================

CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- 1. AUTHENTICATION & ROLE MANAGEMENT
-- ============================================================================

CREATE TABLE IF NOT EXISTS roles (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(50) NOT NULL,
    display_name VARCHAR(100),
    description TEXT,
    hierarchy_level INT NOT NULL CHECK (hierarchy_level BETWEEN 1 AND 100),
    is_system BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX uq_roles_name ON roles(name);

CREATE TABLE IF NOT EXISTS permissions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    resource VARCHAR(100) NOT NULL,
    action VARCHAR(50) NOT NULL,
    description TEXT,
    category VARCHAR(50),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX uq_permissions ON permissions(resource, action);

CREATE TABLE IF NOT EXISTS role_permissions (
    role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
    permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
    granted_by UUID,
    granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (role_id, permission_id)
);

CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    username VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    phone VARCHAR(50),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    is_locked BOOLEAN NOT NULL DEFAULT FALSE,
    lock_reason TEXT,
    password_change_required BOOLEAN NOT NULL DEFAULT TRUE,
    email_verified_at TIMESTAMPTZ,
    last_login_at TIMESTAMPTZ,
    failed_login_attempts INT NOT NULL DEFAULT 0,
    locked_until TIMESTAMPTZ,
    preferences JSONB DEFAULT '{}'::jsonb,
    created_by UUID REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_users_username ON users(username) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uq_users_email ON users(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_users_is_active ON users(is_active) WHERE deleted_at IS NULL;
CREATE INDEX idx_users_is_locked ON users(is_locked) WHERE is_locked = TRUE AND deleted_at IS NULL;
CREATE INDEX idx_users_created_by ON users(created_by);

CREATE TABLE IF NOT EXISTS user_roles (
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
    assigned_by UUID REFERENCES users(id),
    assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (user_id, role_id)
);

CREATE INDEX idx_user_roles_user ON user_roles(user_id);
CREATE INDEX idx_user_roles_role ON user_roles(role_id);

-- ============================================================================
-- 2. BAN & SUSPENSION SYSTEM
-- ============================================================================

CREATE TABLE IF NOT EXISTS banned_users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    banned_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    banned_by UUID NOT NULL REFERENCES users(id),
    reason TEXT NOT NULL,
    banned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMPTZ,
    permanent_ban BOOLEAN NOT NULL DEFAULT FALSE,
    is_reversed BOOLEAN NOT NULL DEFAULT FALSE,
    reversed_by UUID REFERENCES users(id),
    reversed_at TIMESTAMPTZ,
    reversal_reason TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_ban_expiry CHECK (
        (permanent_ban = TRUE AND expires_at IS NULL)
        OR (permanent_ban = FALSE AND expires_at IS NOT NULL)
    )
);

CREATE INDEX idx_banned_users_user ON banned_users(banned_user_id);
CREATE INDEX idx_banned_users_active ON banned_users(banned_user_id)
    WHERE is_reversed = FALSE;
CREATE INDEX idx_banned_users_banned_by ON banned_users(banned_by);

CREATE TABLE IF NOT EXISTS suspended_users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    suspended_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    suspended_by UUID NOT NULL REFERENCES users(id),
    reason TEXT NOT NULL,
    suspended_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMPTZ NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    lifted_by UUID REFERENCES users(id),
    lifted_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_suspension_expiry CHECK (expires_at > suspended_at)
);

CREATE INDEX idx_suspended_users_user ON suspended_users(suspended_user_id);
CREATE INDEX idx_suspended_users_active ON suspended_users(suspended_user_id)
    WHERE is_active = TRUE;
CREATE INDEX idx_suspended_users_suspended_by ON suspended_users(suspended_by);

-- ============================================================================
-- 3. SESSION & LOGIN MANAGEMENT
-- ============================================================================

CREATE TABLE IF NOT EXISTS sessions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash VARCHAR(255) NOT NULL,
    ip_address INET,
    user_agent TEXT,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    expires_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_sessions_user ON sessions(user_id) WHERE is_active = TRUE;
CREATE UNIQUE INDEX uq_sessions_token ON sessions(token_hash);
CREATE INDEX idx_sessions_expires ON sessions(expires_at) WHERE is_active = TRUE;

CREATE TABLE IF NOT EXISTS login_attempts (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID REFERENCES users(id),
    username_attempted VARCHAR(100) NOT NULL,
    ip_address INET NOT NULL,
    user_agent TEXT,
    was_successful BOOLEAN NOT NULL DEFAULT FALSE,
    failure_reason VARCHAR(100),
    attempted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_login_attempts_user ON login_attempts(user_id);
CREATE INDEX idx_login_attempts_username ON login_attempts(username_attempted);
CREATE INDEX idx_login_attempts_ip ON login_attempts(ip_address);
CREATE INDEX idx_login_attempts_time ON login_attempts(attempted_at DESC);

-- ============================================================================
-- 4. CUSTOMER MANAGEMENT
-- ============================================================================

CREATE TABLE IF NOT EXISTS customer_statuses (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    color VARCHAR(7),
    sort_order INT NOT NULL DEFAULT 0,
    is_default BOOLEAN NOT NULL DEFAULT FALSE,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_customer_statuses_name ON customer_statuses(name) WHERE deleted_at IS NULL;

CREATE TABLE IF NOT EXISTS customers (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_type VARCHAR(20) NOT NULL,
    status_id UUID NOT NULL REFERENCES customer_statuses(id),
    owner_id UUID REFERENCES users(id),
    source VARCHAR(100),
    notes TEXT,
    tags TEXT[],
    score INT NOT NULL DEFAULT 0 CHECK (score >= 0 AND score <= 100),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_customer_type CHECK (customer_type IN ('individual', 'company'))
);

CREATE INDEX idx_customers_status ON customers(status_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_customers_owner ON customers(owner_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_customers_type ON customers(customer_type) WHERE deleted_at IS NULL;
CREATE INDEX idx_customers_score ON customers(score DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_customers_tags ON customers USING GIN(tags) WHERE deleted_at IS NULL;
CREATE INDEX idx_customers_created ON customers(created_at DESC) WHERE deleted_at IS NULL;

CREATE TABLE IF NOT EXISTS individual_customers (
    customer_id UUID PRIMARY KEY REFERENCES customers(id) ON DELETE CASCADE,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    middle_name VARCHAR(100),
    date_of_birth DATE,
    gender VARCHAR(20),
    job_title VARCHAR(200),
    company_name VARCHAR(200),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_individual_names ON individual_customers(last_name, first_name);

CREATE TABLE IF NOT EXISTS company_customers (
    customer_id UUID PRIMARY KEY REFERENCES customers(id) ON DELETE CASCADE,
    company_name VARCHAR(255) NOT NULL,
    registration_number VARCHAR(100),
    tax_id VARCHAR(100),
    website VARCHAR(500),
    industry VARCHAR(200),
    company_size VARCHAR(50),
    annual_revenue DECIMAL(15,2),
    founded_date DATE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_company_name ON company_customers(company_name);
CREATE INDEX idx_company_industry ON company_customers(industry);

CREATE TABLE IF NOT EXISTS contact_information (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    type VARCHAR(20) NOT NULL,
    value VARCHAR(500) NOT NULL,
    label VARCHAR(100),
    is_primary BOOLEAN NOT NULL DEFAULT FALSE,
    is_verified BOOLEAN NOT NULL DEFAULT FALSE,
    verified_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_contact_type CHECK (type IN ('email','phone','mobile','fax','website','linkedin','twitter','other'))
);

CREATE INDEX idx_contacts_customer ON contact_information(customer_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_contacts_type ON contact_information(type) WHERE deleted_at IS NULL;
CREATE INDEX idx_contacts_value ON contact_information(value) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uq_contacts_primary ON contact_information(customer_id, type) WHERE is_primary = TRUE AND deleted_at IS NULL;

CREATE TABLE IF NOT EXISTS addresses (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    address_type VARCHAR(20) NOT NULL,
    address_line1 VARCHAR(255) NOT NULL,
    address_line2 VARCHAR(255),
    city VARCHAR(100) NOT NULL,
    state VARCHAR(100),
    postal_code VARCHAR(20),
    country VARCHAR(100) NOT NULL,
    is_primary BOOLEAN NOT NULL DEFAULT FALSE,
    latitude DECIMAL(10,7),
    longitude DECIMAL(10,7),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_address_type CHECK (address_type IN ('billing','shipping','physical','postal','other'))
);

CREATE INDEX idx_addresses_customer ON addresses(customer_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_addresses_country ON addresses(country) WHERE deleted_at IS NULL;
CREATE INDEX idx_addresses_city ON addresses(city) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uq_addresses_primary ON addresses(customer_id, address_type) WHERE is_primary = TRUE AND deleted_at IS NULL;

CREATE TABLE IF NOT EXISTS customer_notes (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    author_id UUID NOT NULL REFERENCES users(id),
    content TEXT NOT NULL,
    type VARCHAR(50) NOT NULL DEFAULT 'general',
    is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_note_type CHECK (type IN ('general','call_summary','meeting_notes','complaint','follow_up','other'))
);

CREATE INDEX idx_notes_customer ON customer_notes(customer_id, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_notes_author ON customer_notes(author_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_notes_pinned ON customer_notes(is_pinned) WHERE is_pinned = TRUE AND deleted_at IS NULL;

-- ============================================================================
-- 5. CUSTOMER & INCIDENT REPORTS
-- ============================================================================

CREATE TABLE IF NOT EXISTS customer_reports (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id),
    reported_by UUID NOT NULL REFERENCES users(id),
    report_type VARCHAR(50) NOT NULL,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    severity VARCHAR(20) NOT NULL DEFAULT 'low',
    status VARCHAR(20) NOT NULL DEFAULT 'open',
    resolution_notes TEXT,
    resolved_by UUID REFERENCES users(id),
    resolved_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_report_type CHECK (report_type IN ('complaint','issue','feedback','escalation','other')),
    CONSTRAINT chk_report_severity CHECK (severity IN ('low','medium','high','critical')),
    CONSTRAINT chk_report_status CHECK (status IN ('open','investigating','resolved','closed'))
);

CREATE INDEX idx_customer_reports_customer ON customer_reports(customer_id);
CREATE INDEX idx_customer_reports_reported_by ON customer_reports(reported_by);
CREATE INDEX idx_customer_reports_status ON customer_reports(status);
CREATE INDEX idx_customer_reports_severity ON customer_reports(severity);

CREATE TABLE IF NOT EXISTS incident_reports (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    reported_by UUID NOT NULL REFERENCES users(id),
    target_user_id UUID REFERENCES users(id),
    incident_type VARCHAR(50) NOT NULL,
    title VARCHAR(255) NOT NULL,
    description TEXT NOT NULL,
    severity VARCHAR(20) NOT NULL DEFAULT 'medium',
    status VARCHAR(20) NOT NULL DEFAULT 'open',
    assigned_to UUID REFERENCES users(id),
    resolution_notes TEXT,
    resolved_by UUID REFERENCES users(id),
    resolved_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_incident_type CHECK (incident_type IN ('suspicious_activity','policy_violation','security_breach','harassment','spam','other')),
    CONSTRAINT chk_incident_severity CHECK (severity IN ('low','medium','high','critical')),
    CONSTRAINT chk_incident_status CHECK (status IN ('open','investigating','resolved','dismissed'))
);

CREATE INDEX idx_incident_reports_reported_by ON incident_reports(reported_by);
CREATE INDEX idx_incident_reports_target ON incident_reports(target_user_id);
CREATE INDEX idx_incident_reports_status ON incident_reports(status);
CREATE INDEX idx_incident_reports_severity ON incident_reports(severity);

-- ============================================================================
-- 6. LEAD MANAGEMENT
-- ============================================================================

CREATE TABLE IF NOT EXISTS lead_sources (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_lead_sources_name ON lead_sources(name) WHERE deleted_at IS NULL;

CREATE TABLE IF NOT EXISTS lead_stages (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    sort_order INT NOT NULL DEFAULT 0,
    probability INT NOT NULL DEFAULT 0 CHECK (probability >= 0 AND probability <= 100),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_lead_stages_name ON lead_stages(name) WHERE deleted_at IS NULL;
CREATE INDEX idx_lead_stages_sort ON lead_stages(sort_order);

CREATE TABLE IF NOT EXISTS leads (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    source_id UUID REFERENCES lead_sources(id),
    stage_id UUID NOT NULL REFERENCES lead_stages(id),
    assigned_to UUID REFERENCES users(id),
    company_name VARCHAR(255),
    contact_name VARCHAR(255) NOT NULL,
    email VARCHAR(255),
    phone VARCHAR(50),
    job_title VARCHAR(200),
    budget_range VARCHAR(100),
    interest_level VARCHAR(20),
    notes TEXT,
    score INT NOT NULL DEFAULT 0 CHECK (score >= 0 AND score <= 100),
    converted_customer_id UUID REFERENCES customers(id),
    converted_at TIMESTAMPTZ,
    converted_by UUID REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_interest_level CHECK (interest_level IN ('low','medium','high')),
    CONSTRAINT chk_conversion_consistency CHECK (
        (converted_customer_id IS NULL AND converted_at IS NULL AND converted_by IS NULL)
        OR (converted_customer_id IS NOT NULL AND converted_at IS NOT NULL AND converted_by IS NOT NULL)
    )
);

CREATE INDEX idx_leads_stage ON leads(stage_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_leads_assigned ON leads(assigned_to) WHERE deleted_at IS NULL;
CREATE INDEX idx_leads_source ON leads(source_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_leads_score ON leads(score DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_leads_created ON leads(created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_leads_converted ON leads(converted_customer_id) WHERE converted_customer_id IS NOT NULL;
CREATE INDEX idx_leads_email ON leads(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_leads_active ON leads(stage_id, assigned_to) WHERE deleted_at IS NULL AND converted_customer_id IS NULL;

CREATE TABLE IF NOT EXISTS lead_conversions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    lead_id UUID NOT NULL REFERENCES leads(id) ON DELETE CASCADE,
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    converted_by UUID NOT NULL REFERENCES users(id),
    conversion_notes TEXT,
    converted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_lead_conversions_lead ON lead_conversions(lead_id);
CREATE INDEX idx_lead_conversions_customer ON lead_conversions(customer_id);

-- ============================================================================
-- 7. PRODUCT CATALOG
-- ============================================================================

CREATE TABLE IF NOT EXISTS product_categories (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    parent_id UUID REFERENCES product_categories(id),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    sort_order INT NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_product_categories_name ON product_categories(name) WHERE deleted_at IS NULL;
CREATE INDEX idx_product_categories_parent ON product_categories(parent_id);

CREATE TABLE IF NOT EXISTS products (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    category_id UUID REFERENCES product_categories(id),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    sku VARCHAR(100),
    unit_price DECIMAL(15,2) NOT NULL CHECK (unit_price >= 0),
    cost_price DECIMAL(15,2) CHECK (cost_price >= 0),
    currency VARCHAR(3) NOT NULL DEFAULT 'USD',
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    is_service BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_products_sku ON products(sku) WHERE sku IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_products_category ON products(category_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_products_name ON products(name) WHERE deleted_at IS NULL;
CREATE INDEX idx_products_active ON products(is_active) WHERE deleted_at IS NULL;

-- ============================================================================
-- 8. SALES PIPELINE
-- ============================================================================

CREATE TABLE IF NOT EXISTS deal_stages (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(100) NOT NULL,
    description TEXT,
    sort_order INT NOT NULL DEFAULT 0,
    probability INT NOT NULL DEFAULT 0 CHECK (probability >= 0 AND probability <= 100),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX uq_deal_stages_name ON deal_stages(name) WHERE deleted_at IS NULL;
CREATE INDEX idx_deal_stages_sort ON deal_stages(sort_order);

CREATE TABLE IF NOT EXISTS opportunities (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id),
    lead_id UUID REFERENCES leads(id),
    stage_id UUID NOT NULL REFERENCES deal_stages(id),
    owner_id UUID NOT NULL REFERENCES users(id),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    estimated_revenue DECIMAL(15,2),
    probability INT CHECK (probability >= 0 AND probability <= 100),
    expected_close_date DATE,
    actual_close_date DATE,
    loss_reason TEXT,
    is_won BOOLEAN,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

CREATE INDEX idx_opportunities_customer ON opportunities(customer_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_stage ON opportunities(stage_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_owner ON opportunities(owner_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_close ON opportunities(expected_close_date) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_revenue ON opportunities(estimated_revenue DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_won ON opportunities(is_won) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_created ON opportunities(created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_opportunities_owner_stage ON opportunities(owner_id, stage_id) WHERE deleted_at IS NULL AND is_won IS NULL;

CREATE TABLE IF NOT EXISTS opportunity_products (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    opportunity_id UUID NOT NULL REFERENCES opportunities(id) ON DELETE CASCADE,
    product_id UUID NOT NULL REFERENCES products(id),
    quantity INT NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(15,2) NOT NULL,
    discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0 CHECK (discount_percent >= 0 AND discount_percent <= 100),
    total_price DECIMAL(15,2),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_opp_products_opportunity ON opportunity_products(opportunity_id);
CREATE INDEX idx_opp_products_product ON opportunity_products(product_id);

-- ============================================================================
-- 9. COMMUNICATION TRACKING
-- ============================================================================

CREATE TABLE IF NOT EXISTS communication_types (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(50) NOT NULL,
    description TEXT,
    icon VARCHAR(50),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX uq_comm_types_name ON communication_types(name);

CREATE TABLE IF NOT EXISTS communications (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id),
    opportunity_id UUID REFERENCES opportunities(id),
    type_id UUID NOT NULL REFERENCES communication_types(id),
    subject VARCHAR(500),
    body TEXT,
    direction VARCHAR(10) NOT NULL,
    created_by UUID NOT NULL REFERENCES users(id),
    started_at TIMESTAMPTZ,
    duration_minutes INT CHECK (duration_minutes >= 0),
    outcome TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_comm_direction CHECK (direction IN ('inbound','outbound'))
);

CREATE INDEX idx_communications_customer ON communications(customer_id, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_communications_opportunity ON communications(opportunity_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_communications_type ON communications(type_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_communications_created_by ON communications(created_by) WHERE deleted_at IS NULL;
CREATE INDEX idx_communications_started ON communications(started_at) WHERE deleted_at IS NULL;

CREATE TABLE IF NOT EXISTS communication_participants (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    communication_id UUID NOT NULL REFERENCES communications(id) ON DELETE CASCADE,
    user_id UUID REFERENCES users(id),
    email VARCHAR(255),
    name VARCHAR(255),
    role VARCHAR(50),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_comm_participants_comm ON communication_participants(communication_id);
CREATE INDEX idx_comm_participants_user ON communication_participants(user_id);

-- ============================================================================
-- 10. TASKS AND ACTIVITIES
-- ============================================================================

CREATE TABLE IF NOT EXISTS task_priorities (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(50) NOT NULL,
    color VARCHAR(7),
    sort_order INT NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX uq_task_priorities_name ON task_priorities(name);

CREATE TABLE IF NOT EXISTS tasks (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID REFERENCES customers(id),
    opportunity_id UUID REFERENCES opportunities(id),
    communication_id UUID REFERENCES communications(id),
    assigned_to UUID REFERENCES users(id),
    assigned_by UUID REFERENCES users(id),
    title VARCHAR(255) NOT NULL,
    description TEXT,
    priority_id UUID REFERENCES task_priorities(id),
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    due_date TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    reminder_at TIMESTAMPTZ,
    reminder_sent BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_task_status CHECK (status IN ('pending','in_progress','completed','cancelled'))
);

CREATE INDEX idx_tasks_assigned_to ON tasks(assigned_to, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_customer ON tasks(customer_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_opportunity ON tasks(opportunity_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_due_date ON tasks(due_date) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_status ON tasks(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_reminder ON tasks(reminder_at) WHERE reminder_sent = FALSE AND deleted_at IS NULL;

-- ============================================================================
-- 11. INVOICE SUPPORT
-- ============================================================================

CREATE TABLE IF NOT EXISTS invoice_statuses (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(50) NOT NULL,
    description TEXT,
    color VARCHAR(7),
    sort_order INT NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX uq_invoice_statuses_name ON invoice_statuses(name);

CREATE TABLE IF NOT EXISTS invoices (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id),
    opportunity_id UUID REFERENCES opportunities(id),
    status_id UUID NOT NULL REFERENCES invoice_statuses(id),
    invoice_number VARCHAR(50) NOT NULL,
    subtotal DECIMAL(15,2) NOT NULL CHECK (subtotal >= 0),
    tax_percent DECIMAL(5,2) CHECK (tax_percent >= 0 AND tax_percent <= 100),
    tax_amount DECIMAL(15,2) CHECK (tax_amount >= 0),
    discount_percent DECIMAL(5,2) CHECK (discount_percent >= 0 AND discount_percent <= 100),
    discount_amount DECIMAL(15,2) CHECK (discount_amount >= 0),
    total_amount DECIMAL(15,2) NOT NULL CHECK (total_amount >= 0),
    currency VARCHAR(3) NOT NULL DEFAULT 'USD',
    issued_date DATE NOT NULL,
    due_date DATE NOT NULL,
    paid_at TIMESTAMPTZ,
    notes TEXT,
    created_by UUID NOT NULL REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_dates CHECK (due_date >= issued_date),
    CONSTRAINT chk_amount_consistency CHECK (total_amount = subtotal + COALESCE(tax_amount,0) - COALESCE(discount_amount,0))
);

CREATE UNIQUE INDEX uq_invoice_number ON invoices(invoice_number) WHERE deleted_at IS NULL;
CREATE INDEX idx_invoices_customer ON invoices(customer_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_invoices_status ON invoices(status_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_invoices_issued ON invoices(issued_date DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_invoices_due ON invoices(due_date) WHERE deleted_at IS NULL;
-- Skipped: cannot use NOW() in partial index predicate (not IMMUTABLE)

CREATE TABLE IF NOT EXISTS invoice_items (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
    product_id UUID REFERENCES products(id),
    description TEXT NOT NULL,
    quantity INT NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(15,2) NOT NULL CHECK (unit_price >= 0),
    discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0 CHECK (discount_percent >= 0 AND discount_percent <= 100),
    total_price DECIMAL(15,2) NOT NULL CHECK (total_price >= 0),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_invoice_items_invoice ON invoice_items(invoice_id);
CREATE INDEX idx_invoice_items_product ON invoice_items(product_id);

CREATE TABLE IF NOT EXISTS payments (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    invoice_id UUID NOT NULL REFERENCES invoices(id),
    amount DECIMAL(15,2) NOT NULL CHECK (amount > 0),
    payment_method VARCHAR(50),
    transaction_id VARCHAR(255),
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    paid_at TIMESTAMPTZ,
    notes TEXT,
    created_by UUID NOT NULL REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,
    CONSTRAINT chk_payment_status CHECK (status IN ('pending','completed','failed','refunded'))
);

CREATE INDEX idx_payments_invoice ON payments(invoice_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_payments_status ON payments(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_payments_transaction ON payments(transaction_id) WHERE transaction_id IS NOT NULL;

-- ============================================================================
-- 12. AUDIT LOGGING
-- ============================================================================

CREATE TABLE IF NOT EXISTS audit_logs (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    table_name VARCHAR(100) NOT NULL,
    record_id UUID NOT NULL,
    action VARCHAR(20) NOT NULL,
    old_data JSONB,
    new_data JSONB,
    changed_by UUID REFERENCES users(id),
    changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    ip_address INET,
    user_agent TEXT,
    session_id UUID,
    CONSTRAINT chk_audit_action CHECK (action IN ('CREATE','UPDATE','DELETE'))
);

CREATE INDEX idx_audit_table_record ON audit_logs(table_name, record_id);
CREATE INDEX idx_audit_changed_by ON audit_logs(changed_by);
CREATE INDEX idx_audit_changed_at ON audit_logs(changed_at DESC);
CREATE INDEX idx_audit_action ON audit_logs(action);
CREATE INDEX idx_audit_table ON audit_logs(table_name);
CREATE INDEX idx_audit_session ON audit_logs(session_id);

-- ============================================================================
-- 13. DUPLICATE DETECTION
-- ============================================================================

CREATE TABLE IF NOT EXISTS customer_duplicates (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    duplicate_customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    match_type VARCHAR(50) NOT NULL,
    match_score DECIMAL(5,2) NOT NULL CHECK (match_score >= 0 AND match_score <= 100),
    is_resolved BOOLEAN NOT NULL DEFAULT FALSE,
    resolved_by UUID REFERENCES users(id),
    resolved_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_no_self_duplicate CHECK (customer_id <> duplicate_customer_id)
);

CREATE INDEX idx_duplicates_customer ON customer_duplicates(customer_id);
CREATE INDEX idx_duplicates_duplicate ON customer_duplicates(duplicate_customer_id);
CREATE INDEX idx_duplicates_unresolved ON customer_duplicates(is_resolved) WHERE is_resolved = FALSE;

-- ============================================================================
-- FULL-TEXT SEARCH INDEXES
-- ============================================================================

CREATE INDEX idx_fts_customers ON customers
    USING GIN (to_tsvector('english', COALESCE(notes, '')));

CREATE INDEX idx_fts_leads ON leads
    USING GIN (to_tsvector('english', COALESCE(contact_name,'') || ' ' || COALESCE(company_name,'') || ' ' || COALESCE(notes,'')));

CREATE INDEX idx_fts_products ON products
    USING GIN (to_tsvector('english', COALESCE(name,'') || ' ' || COALESCE(description,'')));

CREATE INDEX idx_fts_communications ON communications
    USING GIN (to_tsvector('english', COALESCE(subject,'') || ' ' || COALESCE(body,'')));

CREATE INDEX idx_fts_tasks ON tasks
    USING GIN (to_tsvector('english', COALESCE(title,'') || ' ' || COALESCE(description,'')));

-- ============================================================================
-- RBAC TRIGGER: Auto-update updated_at
-- ============================================================================

DO $$
DECLARE
    t TEXT;
BEGIN
    FOR t IN
        SELECT table_name FROM information_schema.tables
        WHERE table_schema = 'public'
          AND table_type = 'BASE TABLE'
          AND table_name IN (
              SELECT table_name FROM information_schema.columns
              WHERE column_name = 'updated_at'
                AND table_schema = 'public'
          )
    LOOP
        EXECUTE format(
            'DROP TRIGGER IF EXISTS trg_%I_updated_at ON %I; CREATE TRIGGER trg_%I_updated_at
             BEFORE UPDATE ON %I
             FOR EACH ROW
             EXECUTE FUNCTION update_updated_at_column()',
            t, t, t, t
        );
    END LOOP;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- RBAC FUNCTION: Check if user has a specific permission
-- ============================================================================

CREATE OR REPLACE FUNCTION has_permission(
    p_user_id UUID,
    p_resource VARCHAR(100),
    p_action VARCHAR(50)
)
RETURNS BOOLEAN AS $$
BEGIN
    RETURN EXISTS (
        SELECT 1
        FROM user_roles ur
        JOIN role_permissions rp ON rp.role_id = ur.role_id
        JOIN permissions p ON p.id = rp.permission_id
        WHERE ur.user_id = p_user_id
          AND p.resource = p_resource
          AND p.action = p_action
    );
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- ============================================================================
-- RBAC FUNCTION: Get user's hierarchy level (lower number = higher privilege)
-- ============================================================================

CREATE OR REPLACE FUNCTION user_hierarchy_level(p_user_id UUID)
RETURNS INT AS $$
    SELECT MIN(r.hierarchy_level)
    FROM user_roles ur
    JOIN roles r ON r.id = ur.role_id
    WHERE ur.user_id = p_user_id;
$$ LANGUAGE sql STABLE SECURITY DEFINER;

-- ============================================================================
-- RBAC FUNCTION: Enforce that only SUPER_ADMIN can create users
-- ============================================================================

CREATE OR REPLACE FUNCTION enforce_create_user()
RETURNS TRIGGER AS $$
DECLARE
    creator_level INT;
    super_admin_level INT := 1;
BEGIN
    -- Allow bootstrap: first user or system migrations with NULL created_by
    IF NEW.created_by IS NULL THEN
        RETURN NEW;
    END IF;

    -- Get the creating user's hierarchy level
    SELECT MIN(r.hierarchy_level) INTO creator_level
    FROM user_roles ur
    JOIN roles r ON r.id = ur.role_id
    WHERE ur.user_id = NEW.created_by;

    IF creator_level IS NULL OR creator_level != super_admin_level THEN
        RAISE EXCEPTION 'ACCESS DENIED: Only SUPER_ADMIN (hierarchy_level=1) can create user accounts. Your level: %',
            COALESCE(creator_level::TEXT, 'NONE');
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

DROP TRIGGER IF EXISTS trg_enforce_create_user ON users;
CREATE TRIGGER trg_enforce_create_user
    BEFORE INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION enforce_create_user();

-- ============================================================================
-- RBAC FUNCTION: Prevent privilege escalation on role assignment
-- ============================================================================

CREATE OR REPLACE FUNCTION enforce_role_assignment()
RETURNS TRIGGER AS $$
DECLARE
    assigner_level INT;
    target_role_level INT;
BEGIN
    -- Allow bootstrap: first role assignment with NULL assigned_by
    IF NEW.assigned_by IS NULL THEN
        RETURN NEW;
    END IF;

    -- Level of the user doing the assignment
    SELECT MIN(r.hierarchy_level) INTO assigner_level
    FROM user_roles ur
    JOIN roles r ON r.id = ur.role_id
    WHERE ur.user_id = NEW.assigned_by;

    -- Level of the role being assigned
    SELECT hierarchy_level INTO target_role_level
    FROM roles
    WHERE id = NEW.role_id;

    IF assigner_level IS NULL THEN
        RAISE EXCEPTION 'ACCESS DENIED: Cannot assign roles. User has no role.';
    END IF;

    -- Only SUPER_ADMIN (level 1) can assign roles
    IF assigner_level != 1 THEN
        RAISE EXCEPTION 'ACCESS DENIED: Only SUPER_ADMIN can assign roles. Your hierarchy level: %', assigner_level;
    END IF;

    -- Prevent privilege escalation: cannot assign SUPER_ADMIN role unless you are SUPER_ADMIN
    IF target_role_level < assigner_level THEN
        RAISE EXCEPTION 'ACCESS DENIED: Privilege escalation prevented. Cannot assign a role (level %) higher privileged than your own (level %).',
            target_role_level, assigner_level;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

DROP TRIGGER IF EXISTS trg_enforce_role_assignment ON user_roles;
CREATE TRIGGER trg_enforce_role_assignment
    BEFORE INSERT ON user_roles
    FOR EACH ROW
    EXECUTE FUNCTION enforce_role_assignment();

-- ============================================================================
-- AUDIT TRIGGER FUNCTION
-- ============================================================================

CREATE OR REPLACE FUNCTION audit_trigger_function()
RETURNS TRIGGER AS $$
DECLARE
    audit_action VARCHAR(20);
    old_row JSONB;
    new_row JSONB;
    excluded_cols TEXT[] := ARRAY['updated_at', 'last_login_at'];
BEGIN
    IF TG_OP = 'INSERT' THEN
        audit_action := 'CREATE';
        old_row := NULL;
        new_row := to_jsonb(NEW);
    ELSIF TG_OP = 'UPDATE' THEN
        audit_action := 'UPDATE';
        old_row := to_jsonb(OLD);
        new_row := to_jsonb(NEW);
    ELSIF TG_OP = 'DELETE' THEN
        audit_action := 'DELETE';
        old_row := to_jsonb(OLD);
        new_row := NULL;
    END IF;

    INSERT INTO audit_logs (table_name, record_id, action, old_data, new_data, changed_by)
    VALUES (
        TG_TABLE_NAME,
        COALESCE(NEW.id, OLD.id),
        audit_action,
        CASE WHEN audit_action IN ('UPDATE', 'DELETE') THEN old_row ELSE NULL END,
        CASE WHEN audit_action IN ('CREATE', 'UPDATE') THEN new_row ELSE NULL END,
        NULLIF(current_setting('app.current_user_id', true), '')
    );

    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- ============================================================================
-- AUDIT TRIGGER: Ban actions
-- ============================================================================

CREATE OR REPLACE FUNCTION audit_ban_action()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO audit_logs (table_name, record_id, action, new_data, changed_by)
        VALUES ('banned_users', NEW.id, 'CREATE', to_jsonb(NEW), NEW.banned_by);
    ELSIF TG_OP = 'UPDATE' AND NEW.is_reversed = TRUE AND OLD.is_reversed = FALSE THEN
        INSERT INTO audit_logs (table_name, record_id, action, old_data, new_data, changed_by)
        VALUES ('banned_users', NEW.id, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), NEW.reversed_by);
    END IF;
    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

DROP TRIGGER IF EXISTS trg_audit_ban ON banned_users;
CREATE TRIGGER trg_audit_ban
    AFTER INSERT OR UPDATE ON banned_users
    FOR EACH ROW
    EXECUTE FUNCTION audit_ban_action();

-- ============================================================================
-- AUDIT TRIGGER: Suspension actions
-- ============================================================================

CREATE OR REPLACE FUNCTION audit_suspension_action()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO audit_logs (table_name, record_id, action, new_data, changed_by)
        VALUES ('suspended_users', NEW.id, 'CREATE', to_jsonb(NEW), NEW.suspended_by);
    ELSIF TG_OP = 'UPDATE' AND OLD.is_active = TRUE AND NEW.is_active = FALSE THEN
        INSERT INTO audit_logs (table_name, record_id, action, old_data, new_data, changed_by)
        VALUES ('suspended_users', NEW.id, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), NEW.lifted_by);
    END IF;
    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

DROP TRIGGER IF EXISTS trg_audit_suspension ON suspended_users;
CREATE TRIGGER trg_audit_suspension
    AFTER INSERT OR UPDATE ON suspended_users
    FOR EACH ROW
    EXECUTE FUNCTION audit_suspension_action();

-- ============================================================================
-- AUDIT TRIGGER: Login/logout actions
-- ============================================================================

CREATE OR REPLACE FUNCTION audit_login()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.was_successful = TRUE THEN
        INSERT INTO audit_logs (table_name, record_id, action, new_data, changed_by)
        VALUES (
            'login_attempts',
            NEW.id,
            'CREATE',
            jsonb_build_object(
                'username', NEW.username_attempted,
                'successful', TRUE,
                'ip', NEW.ip_address::TEXT
            ),
            NEW.user_id
        );
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

DROP TRIGGER IF EXISTS trg_audit_login ON login_attempts;
CREATE TRIGGER trg_audit_login
    AFTER INSERT ON login_attempts
    FOR EACH ROW
    WHEN (NEW.was_successful = TRUE)
    EXECUTE FUNCTION audit_login();

-- ============================================================================
-- VIEW: Active bans with user details
-- ============================================================================

CREATE VIEW v_active_bans AS
SELECT
    b.id AS ban_id,
    bu.id AS banned_user_id,
    bu.username AS banned_username,
    bu.email AS banned_email,
    ba.id AS banned_by_id,
    ba.username AS banned_by_username,
    b.reason,
    b.banned_at,
    b.expires_at,
    b.permanent_ban,
    CASE
        WHEN b.permanent_ban THEN 'PERMANENT'
        WHEN b.expires_at > NOW() THEN 'ACTIVE'
        ELSE 'EXPIRED'
    END AS ban_status
FROM banned_users b
JOIN users bu ON bu.id = b.banned_user_id
JOIN users ba ON ba.id = b.banned_by
WHERE b.is_reversed = FALSE
  AND (b.permanent_ban = TRUE OR b.expires_at > NOW());

-- ============================================================================
-- VIEW: Active suspensions
-- ============================================================================

CREATE VIEW v_active_suspensions AS
SELECT
    s.id AS suspension_id,
    su.id AS suspended_user_id,
    su.username AS suspended_username,
    su.email AS suspended_email,
    sa.id AS suspended_by_id,
    sa.username AS suspended_by_username,
    s.reason,
    s.suspended_at,
    s.expires_at
FROM suspended_users s
JOIN users su ON su.id = s.suspended_user_id
JOIN users sa ON sa.id = s.suspended_by
WHERE s.is_active = TRUE AND s.expires_at > NOW();

-- ============================================================================
-- VIEW: User permission report
-- ============================================================================

CREATE VIEW v_user_permissions AS
SELECT
    u.id AS user_id,
    u.username,
    STRING_AGG(DISTINCT r.name, ', ') AS roles,
    JSONB_AGG(DISTINCT jsonb_build_object('resource', p.resource, 'action', p.action))
        FILTER (WHERE p.id IS NOT NULL) AS permissions
FROM users u
LEFT JOIN user_roles ur ON ur.user_id = u.id
LEFT JOIN roles r ON r.id = ur.role_id
LEFT JOIN role_permissions rp ON rp.role_id = r.id
LEFT JOIN permissions p ON p.id = rp.permission_id
WHERE u.deleted_at IS NULL
GROUP BY u.id, u.username;

-- ============================================================================
-- VIEW: Revenue Analytics
-- ============================================================================

CREATE VIEW v_revenue_analytics AS
SELECT
    DATE_TRUNC('month', i.issued_date) AS month,
    i.currency,
    COUNT(DISTINCT i.id) AS invoice_count,
    SUM(i.total_amount) AS total_revenue,
    SUM(CASE WHEN s.name = 'Paid' THEN i.total_amount ELSE 0 END) AS collected_revenue,
    COUNT(DISTINCT i.customer_id) AS paying_customers,
    AVG(i.total_amount) AS avg_invoice_value
FROM invoices i
JOIN invoice_statuses s ON s.id = i.status_id
WHERE i.deleted_at IS NULL
GROUP BY DATE_TRUNC('month', i.issued_date), i.currency
ORDER BY month DESC;

-- ============================================================================
-- VIEW: Sales Performance
-- ============================================================================

CREATE VIEW v_sales_performance AS
SELECT
    u.id AS user_id,
    u.first_name || ' ' || u.last_name AS user_name,
    COUNT(DISTINCT o.id) AS total_opportunities,
    COUNT(DISTINCT CASE WHEN o.is_won = TRUE THEN o.id END) AS won_opportunities,
    COUNT(DISTINCT CASE WHEN o.is_won = FALSE AND o.actual_close_date IS NOT NULL THEN o.id END) AS lost_opportunities,
    COALESCE(SUM(CASE WHEN o.is_won = TRUE THEN o.estimated_revenue ELSE 0 END), 0) AS won_revenue,
    COALESCE(AVG(CASE WHEN o.is_won = TRUE THEN o.estimated_revenue ELSE NULL END), 0) AS avg_deal_size,
    COUNT(DISTINCT l.id) AS total_leads,
    COUNT(DISTINCT l.converted_customer_id) AS converted_leads,
    COUNT(DISTINCT t.id) AS completed_tasks
FROM users u
LEFT JOIN opportunities o ON o.owner_id = u.id AND o.deleted_at IS NULL
LEFT JOIN leads l ON l.assigned_to = u.id AND l.deleted_at IS NULL
LEFT JOIN tasks t ON t.assigned_to = u.id AND t.status = 'completed' AND t.deleted_at IS NULL
WHERE u.deleted_at IS NULL
GROUP BY u.id, u.first_name, u.last_name;
