-- =====================================================
-- SCHEMA: analytics
-- PROPÓSITO: Contabilidad analítica, tracking de costos/ingresos
-- MÓDULOS: MGN-008 (Contabilidad Analítica)
-- FECHA: 2025-11-24
-- =====================================================

-- Crear schema
CREATE SCHEMA IF NOT EXISTS analytics;

-- =====================================================
-- TYPES (ENUMs)
-- =====================================================

CREATE TYPE analytics.account_type AS ENUM (
  'project',
  'department',
  'cost_center',
  'customer',
  'product',
  'other'
);

CREATE TYPE analytics.line_type AS ENUM (
  'expense',
  'income',
  'timesheet'
);

CREATE TYPE analytics.account_status AS ENUM (
  'active',
  'inactive',
  'closed'
);

-- =====================================================
-- TABLES
-- =====================================================

-- Tabla: analytic_plans (Planes analíticos - multi-dimensional)
CREATE TABLE analytics.analytic_plans (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID REFERENCES auth.companies(id),

  name VARCHAR(255) NOT NULL,
  description TEXT,

  -- Control
  active BOOLEAN NOT NULL DEFAULT TRUE,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_analytic_plans_name_tenant UNIQUE (tenant_id, name)
);

-- Tabla: analytic_accounts (Cuentas analíticas)
CREATE TABLE analytics.analytic_accounts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,

  plan_id UUID REFERENCES analytics.analytic_plans(id),

  -- Identificación
  name VARCHAR(255) NOT NULL,
  code VARCHAR(50),
  account_type analytics.account_type NOT NULL DEFAULT 'other',

  -- Jerarquía
  parent_id UUID REFERENCES analytics.analytic_accounts(id),
  full_path TEXT, -- Generado automáticamente

  -- Referencias
  partner_id UUID REFERENCES core.partners(id), -- Cliente/proveedor asociado

  -- Presupuesto
  budget DECIMAL(15, 2) DEFAULT 0,

  -- Estado
  status analytics.account_status NOT NULL DEFAULT 'active',

  -- Fechas
  date_start DATE,
  date_end DATE,

  -- Notas
  description TEXT,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),
  deleted_at TIMESTAMP,
  deleted_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_analytic_accounts_code_company UNIQUE (company_id, code),
  CONSTRAINT chk_analytic_accounts_no_self_parent CHECK (id != parent_id),
  CONSTRAINT chk_analytic_accounts_budget CHECK (budget >= 0),
  CONSTRAINT chk_analytic_accounts_dates CHECK (date_end IS NULL OR date_end >= date_start)
);

-- Tabla: analytic_tags (Etiquetas analíticas - clasificación cross-cutting)
CREATE TABLE analytics.analytic_tags (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  name VARCHAR(100) NOT NULL,
  color VARCHAR(20), -- Color hex
  description TEXT,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_analytic_tags_name_tenant UNIQUE (tenant_id, name)
);

-- Tabla: cost_centers (Centros de costo)
CREATE TABLE analytics.cost_centers (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,

  name VARCHAR(255) NOT NULL,
  code VARCHAR(50),
  analytic_account_id UUID NOT NULL REFERENCES analytics.analytic_accounts(id),

  -- Responsable
  manager_id UUID REFERENCES auth.users(id),

  -- Presupuesto
  budget_monthly DECIMAL(15, 2) DEFAULT 0,
  budget_annual DECIMAL(15, 2) DEFAULT 0,

  -- Control
  active BOOLEAN NOT NULL DEFAULT TRUE,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_cost_centers_code_company UNIQUE (company_id, code)
);

-- Tabla: analytic_lines (Líneas analíticas - registro de costos/ingresos)
CREATE TABLE analytics.analytic_lines (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,

  analytic_account_id UUID NOT NULL REFERENCES analytics.analytic_accounts(id),

  -- Fecha
  date DATE NOT NULL,

  -- Montos
  amount DECIMAL(15, 2) NOT NULL, -- Negativo=costo, Positivo=ingreso
  unit_amount DECIMAL(12, 4) DEFAULT 0, -- Horas para timesheet, cantidades para productos

  -- Tipo
  line_type analytics.line_type NOT NULL,

  -- Referencias
  product_id UUID REFERENCES inventory.products(id),
  employee_id UUID, -- FK a hr.employees (se crea después)
  partner_id UUID REFERENCES core.partners(id),

  -- Descripción
  name VARCHAR(255),
  description TEXT,

  -- Documento origen (polimórfico)
  source_model VARCHAR(100), -- 'Invoice', 'PurchaseOrder', 'SaleOrder', 'Timesheet', etc.
  source_id UUID,
  source_document VARCHAR(255), -- "invoice/123", "purchase_order/456"

  -- Moneda
  currency_id UUID REFERENCES core.currencies(id),

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),

  CONSTRAINT chk_analytic_lines_unit_amount CHECK (unit_amount >= 0)
);

-- Tabla: analytic_line_tags (Many-to-many: líneas analíticas - tags)
CREATE TABLE analytics.analytic_line_tags (
  analytic_line_id UUID NOT NULL REFERENCES analytics.analytic_lines(id) ON DELETE CASCADE,
  analytic_tag_id UUID NOT NULL REFERENCES analytics.analytic_tags(id) ON DELETE CASCADE,

  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  PRIMARY KEY (analytic_line_id, analytic_tag_id)
);

-- Tabla: analytic_distributions (Distribución analítica multi-cuenta)
CREATE TABLE analytics.analytic_distributions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

  -- Línea origen (polimórfico)
  source_model VARCHAR(100) NOT NULL, -- 'PurchaseOrderLine', 'InvoiceLine', etc.
  source_id UUID NOT NULL,

  -- Cuenta analítica destino
  analytic_account_id UUID NOT NULL REFERENCES analytics.analytic_accounts(id),

  -- Distribución
  percentage DECIMAL(5, 2) NOT NULL, -- 0-100
  amount DECIMAL(15, 2), -- Calculado automáticamente

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT chk_analytic_distributions_percentage CHECK (percentage >= 0 AND percentage <= 100)
);

-- =====================================================
-- INDICES
-- =====================================================

-- Analytic Plans
CREATE INDEX idx_analytic_plans_tenant_id ON analytics.analytic_plans(tenant_id);
CREATE INDEX idx_analytic_plans_active ON analytics.analytic_plans(active) WHERE active = TRUE;

-- Analytic Accounts
CREATE INDEX idx_analytic_accounts_tenant_id ON analytics.analytic_accounts(tenant_id);
CREATE INDEX idx_analytic_accounts_company_id ON analytics.analytic_accounts(company_id);
CREATE INDEX idx_analytic_accounts_plan_id ON analytics.analytic_accounts(plan_id);
CREATE INDEX idx_analytic_accounts_parent_id ON analytics.analytic_accounts(parent_id);
CREATE INDEX idx_analytic_accounts_partner_id ON analytics.analytic_accounts(partner_id);
CREATE INDEX idx_analytic_accounts_code ON analytics.analytic_accounts(code);
CREATE INDEX idx_analytic_accounts_type ON analytics.analytic_accounts(account_type);
CREATE INDEX idx_analytic_accounts_status ON analytics.analytic_accounts(status);

-- Analytic Tags
CREATE INDEX idx_analytic_tags_tenant_id ON analytics.analytic_tags(tenant_id);
CREATE INDEX idx_analytic_tags_name ON analytics.analytic_tags(name);

-- Cost Centers
CREATE INDEX idx_cost_centers_tenant_id ON analytics.cost_centers(tenant_id);
CREATE INDEX idx_cost_centers_company_id ON analytics.cost_centers(company_id);
CREATE INDEX idx_cost_centers_analytic_account_id ON analytics.cost_centers(analytic_account_id);
CREATE INDEX idx_cost_centers_manager_id ON analytics.cost_centers(manager_id);
CREATE INDEX idx_cost_centers_active ON analytics.cost_centers(active) WHERE active = TRUE;

-- Analytic Lines
CREATE INDEX idx_analytic_lines_tenant_id ON analytics.analytic_lines(tenant_id);
CREATE INDEX idx_analytic_lines_company_id ON analytics.analytic_lines(company_id);
CREATE INDEX idx_analytic_lines_analytic_account_id ON analytics.analytic_lines(analytic_account_id);
CREATE INDEX idx_analytic_lines_date ON analytics.analytic_lines(date);
CREATE INDEX idx_analytic_lines_line_type ON analytics.analytic_lines(line_type);
CREATE INDEX idx_analytic_lines_product_id ON analytics.analytic_lines(product_id);
CREATE INDEX idx_analytic_lines_employee_id ON analytics.analytic_lines(employee_id);
CREATE INDEX idx_analytic_lines_source ON analytics.analytic_lines(source_model, source_id);

-- Analytic Line Tags
CREATE INDEX idx_analytic_line_tags_line_id ON analytics.analytic_line_tags(analytic_line_id);
CREATE INDEX idx_analytic_line_tags_tag_id ON analytics.analytic_line_tags(analytic_tag_id);

-- Analytic Distributions
CREATE INDEX idx_analytic_distributions_source ON analytics.analytic_distributions(source_model, source_id);
CREATE INDEX idx_analytic_distributions_analytic_account_id ON analytics.analytic_distributions(analytic_account_id);

-- =====================================================
-- FUNCTIONS
-- =====================================================

-- Función: update_analytic_account_path
CREATE OR REPLACE FUNCTION analytics.update_analytic_account_path()
RETURNS TRIGGER AS $$
DECLARE
  v_parent_path TEXT;
BEGIN
  IF NEW.parent_id IS NULL THEN
    NEW.full_path := NEW.name;
  ELSE
    SELECT full_path INTO v_parent_path
    FROM analytics.analytic_accounts
    WHERE id = NEW.parent_id;

    NEW.full_path := v_parent_path || ' / ' || NEW.name;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION analytics.update_analytic_account_path IS 'Actualiza el path completo de la cuenta analítica';

-- Función: get_analytic_balance
CREATE OR REPLACE FUNCTION analytics.get_analytic_balance(
  p_analytic_account_id UUID,
  p_date_from DATE DEFAULT NULL,
  p_date_to DATE DEFAULT NULL
)
RETURNS TABLE(
  total_income DECIMAL,
  total_expense DECIMAL,
  balance DECIMAL
) AS $$
BEGIN
  RETURN QUERY
  SELECT
    COALESCE(SUM(CASE WHEN line_type = 'income' THEN amount ELSE 0 END), 0) AS total_income,
    COALESCE(SUM(CASE WHEN line_type = 'expense' THEN ABS(amount) ELSE 0 END), 0) AS total_expense,
    COALESCE(SUM(amount), 0) AS balance
  FROM analytics.analytic_lines
  WHERE analytic_account_id = p_analytic_account_id
    AND (p_date_from IS NULL OR date >= p_date_from)
    AND (p_date_to IS NULL OR date <= p_date_to);
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION analytics.get_analytic_balance IS 'Obtiene el balance de una cuenta analítica en un período';

-- Función: validate_distribution_100_percent
CREATE OR REPLACE FUNCTION analytics.validate_distribution_100_percent()
RETURNS TRIGGER AS $$
DECLARE
  v_total_percentage DECIMAL;
BEGIN
  SELECT COALESCE(SUM(percentage), 0)
  INTO v_total_percentage
  FROM analytics.analytic_distributions
  WHERE source_model = NEW.source_model
    AND source_id = NEW.source_id;

  IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
    v_total_percentage := v_total_percentage + NEW.percentage;
  END IF;

  IF v_total_percentage > 100 THEN
    RAISE EXCEPTION 'Total distribution percentage cannot exceed 100%% (currently: %%)', v_total_percentage;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION analytics.validate_distribution_100_percent IS 'Valida que la distribución analítica no exceda el 100%';

-- Función: create_analytic_line_from_invoice
CREATE OR REPLACE FUNCTION analytics.create_analytic_line_from_invoice(p_invoice_line_id UUID)
RETURNS UUID AS $$
DECLARE
  v_line RECORD;
  v_invoice RECORD;
  v_analytic_line_id UUID;
  v_amount DECIMAL;
BEGIN
  -- Obtener datos de la línea de factura
  SELECT il.*, i.invoice_type, i.company_id, i.tenant_id, i.partner_id, i.invoice_date
  INTO v_line
  FROM financial.invoice_lines il
  JOIN financial.invoices i ON il.invoice_id = i.id
  WHERE il.id = p_invoice_line_id;

  IF NOT FOUND OR v_line.analytic_account_id IS NULL THEN
    RETURN NULL; -- Sin cuenta analítica, no crear línea
  END IF;

  -- Determinar monto (negativo para compras, positivo para ventas)
  IF v_line.invoice_type = 'supplier' THEN
    v_amount := -ABS(v_line.amount_total);
  ELSE
    v_amount := v_line.amount_total;
  END IF;

  -- Crear línea analítica
  INSERT INTO analytics.analytic_lines (
    tenant_id,
    company_id,
    analytic_account_id,
    date,
    amount,
    unit_amount,
    line_type,
    product_id,
    partner_id,
    name,
    description,
    source_model,
    source_id,
    source_document
  ) VALUES (
    v_line.tenant_id,
    v_line.company_id,
    v_line.analytic_account_id,
    v_line.invoice_date,
    v_amount,
    v_line.quantity,
    CASE WHEN v_line.invoice_type = 'supplier' THEN 'expense'::analytics.line_type ELSE 'income'::analytics.line_type END,
    v_line.product_id,
    v_line.partner_id,
    v_line.description,
    v_line.description,
    'InvoiceLine',
    v_line.id,
    'invoice_line/' || v_line.id::TEXT
  ) RETURNING id INTO v_analytic_line_id;

  RETURN v_analytic_line_id;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION analytics.create_analytic_line_from_invoice IS 'Crea una línea analítica a partir de una línea de factura';

-- =====================================================
-- TRIGGERS
-- =====================================================

CREATE TRIGGER trg_analytic_plans_updated_at
  BEFORE UPDATE ON analytics.analytic_plans
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_analytic_accounts_updated_at
  BEFORE UPDATE ON analytics.analytic_accounts
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_cost_centers_updated_at
  BEFORE UPDATE ON analytics.cost_centers
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

-- Trigger: Actualizar full_path de cuenta analítica
CREATE TRIGGER trg_analytic_accounts_update_path
  BEFORE INSERT OR UPDATE OF name, parent_id ON analytics.analytic_accounts
  FOR EACH ROW
  EXECUTE FUNCTION analytics.update_analytic_account_path();

-- Trigger: Validar distribución 100%
CREATE TRIGGER trg_analytic_distributions_validate_100
  BEFORE INSERT OR UPDATE ON analytics.analytic_distributions
  FOR EACH ROW
  EXECUTE FUNCTION analytics.validate_distribution_100_percent();

-- =====================================================
-- ROW LEVEL SECURITY (RLS)
-- =====================================================

ALTER TABLE analytics.analytic_plans ENABLE ROW LEVEL SECURITY;
ALTER TABLE analytics.analytic_accounts ENABLE ROW LEVEL SECURITY;
ALTER TABLE analytics.analytic_tags ENABLE ROW LEVEL SECURITY;
ALTER TABLE analytics.cost_centers ENABLE ROW LEVEL SECURITY;
ALTER TABLE analytics.analytic_lines ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_analytic_plans ON analytics.analytic_plans
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_analytic_accounts ON analytics.analytic_accounts
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_analytic_tags ON analytics.analytic_tags
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_cost_centers ON analytics.cost_centers
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_analytic_lines ON analytics.analytic_lines
  USING (tenant_id = get_current_tenant_id());

-- =====================================================
-- COMENTARIOS
-- =====================================================

COMMENT ON SCHEMA analytics IS 'Schema de contabilidad analítica y tracking de costos/ingresos';
COMMENT ON TABLE analytics.analytic_plans IS 'Planes analíticos para análisis multi-dimensional';
COMMENT ON TABLE analytics.analytic_accounts IS 'Cuentas analíticas (proyectos, departamentos, centros de costo)';
COMMENT ON TABLE analytics.analytic_tags IS 'Etiquetas analíticas para clasificación cross-cutting';
COMMENT ON TABLE analytics.cost_centers IS 'Centros de costo con presupuestos';
COMMENT ON TABLE analytics.analytic_lines IS 'Líneas analíticas de costos e ingresos';
COMMENT ON TABLE analytics.analytic_line_tags IS 'Relación many-to-many entre líneas y tags';
COMMENT ON TABLE analytics.analytic_distributions IS 'Distribución de montos a múltiples cuentas analíticas';

-- =====================================================
-- VISTAS ÚTILES
-- =====================================================

-- Vista: balance analítico por cuenta
CREATE OR REPLACE VIEW analytics.analytic_balance_view AS
SELECT
  aa.id AS analytic_account_id,
  aa.code,
  aa.name,
  aa.budget,
  COALESCE(SUM(CASE WHEN al.line_type = 'income' THEN al.amount ELSE 0 END), 0) AS total_income,
  COALESCE(SUM(CASE WHEN al.line_type = 'expense' THEN ABS(al.amount) ELSE 0 END), 0) AS total_expense,
  COALESCE(SUM(al.amount), 0) AS balance,
  aa.budget - COALESCE(SUM(CASE WHEN al.line_type = 'expense' THEN ABS(al.amount) ELSE 0 END), 0) AS budget_variance
FROM analytics.analytic_accounts aa
LEFT JOIN analytics.analytic_lines al ON aa.id = al.analytic_account_id
WHERE aa.deleted_at IS NULL
GROUP BY aa.id, aa.code, aa.name, aa.budget;

COMMENT ON VIEW analytics.analytic_balance_view IS 'Vista de balance analítico por cuenta con presupuesto vs real';

-- =====================================================
-- FIN DEL SCHEMA ANALYTICS
-- =====================================================
