-- =====================================================
-- Reporting & Analytics RPC Functions
--
-- Enterprise-grade reporting: demographics, retention,
-- geographic distribution, dues collection, membership
-- duration analysis.
-- =====================================================

-- 1. Age demographics by gender
CREATE OR REPLACE FUNCTION public.get_member_demographics(p_account_id uuid)
RETURNS TABLE (
  age_group text,
  male_count bigint,
  female_count bigint,
  diverse_count bigint,
  unknown_count bigint,
  total bigint
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = ''
AS $$
BEGIN
  IF NOT public.has_role_on_account(p_account_id) THEN
    RAISE EXCEPTION 'Access denied';
  END IF;

  RETURN QUERY
  SELECT
    CASE
      WHEN age < 18 THEN 'Unter 18'
      WHEN age BETWEEN 18 AND 30 THEN '18-30'
      WHEN age BETWEEN 31 AND 50 THEN '31-50'
      WHEN age BETWEEN 51 AND 65 THEN '51-65'
      WHEN age > 65 THEN 'Über 65'
      ELSE 'Unbekannt'
    END AS age_group,
    count(*) FILTER (WHERE m.gender = 'male') AS male_count,
    count(*) FILTER (WHERE m.gender = 'female') AS female_count,
    count(*) FILTER (WHERE m.gender = 'diverse') AS diverse_count,
    count(*) FILTER (WHERE m.gender IS NULL OR m.gender NOT IN ('male', 'female', 'diverse')) AS unknown_count,
    count(*) AS total
  FROM public.members m
  LEFT JOIN LATERAL (
    SELECT CASE
      WHEN m.date_of_birth IS NOT NULL THEN
        extract(year FROM age(current_date, m.date_of_birth))::int
      ELSE NULL
    END AS age
  ) ages ON true
  WHERE m.account_id = p_account_id
    AND m.status = 'active'
    AND m.is_archived = false
  GROUP BY age_group
  ORDER BY
    CASE age_group
      WHEN 'Unter 18' THEN 1
      WHEN '18-30' THEN 2
      WHEN '31-50' THEN 3
      WHEN '51-65' THEN 4
      WHEN 'Über 65' THEN 5
      ELSE 6
    END;
END;
$$;

GRANT EXECUTE ON FUNCTION public.get_member_demographics(uuid) TO authenticated;

-- 2. Year-over-year membership retention
CREATE OR REPLACE FUNCTION public.get_member_retention(
  p_account_id uuid,
  p_years int DEFAULT 5
)
RETURNS TABLE (
  year int,
  members_start bigint,
  new_members bigint,
  resigned_members bigint,
  members_end bigint,
  retention_rate numeric
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = ''
AS $$
BEGIN
  IF NOT public.has_role_on_account(p_account_id) THEN
    RAISE EXCEPTION 'Access denied';
  END IF;

  RETURN QUERY
  WITH years AS (
    SELECT generate_series(
      extract(year FROM current_date)::int - p_years + 1,
      extract(year FROM current_date)::int
    ) AS yr
  ),
  stats AS (
    SELECT
      y.yr,
      count(*) FILTER (WHERE m.entry_date < make_date(y.yr, 1, 1)
        AND (m.exit_date IS NULL OR m.exit_date >= make_date(y.yr, 1, 1))) AS members_start,
      count(*) FILTER (WHERE extract(year FROM m.entry_date) = y.yr) AS new_members,
      count(*) FILTER (WHERE extract(year FROM m.exit_date) = y.yr) AS resigned_members,
      count(*) FILTER (WHERE m.entry_date <= make_date(y.yr, 12, 31)
        AND (m.exit_date IS NULL OR m.exit_date > make_date(y.yr, 12, 31))) AS members_end
    FROM years y
    CROSS JOIN public.members m
    WHERE m.account_id = p_account_id AND m.is_archived = false
    GROUP BY y.yr
  )
  SELECT
    s.yr AS year,
    s.members_start,
    s.new_members,
    s.resigned_members,
    s.members_end,
    CASE WHEN s.members_start > 0
      THEN round((s.members_end::numeric / s.members_start) * 100, 1)
      ELSE 0
    END AS retention_rate
  FROM stats s
  ORDER BY s.yr;
END;
$$;

GRANT EXECUTE ON FUNCTION public.get_member_retention(uuid, int) TO authenticated;

-- 3. Geographic distribution by postal code prefix
CREATE OR REPLACE FUNCTION public.get_member_geographic_distribution(p_account_id uuid)
RETURNS TABLE (
  postal_prefix text,
  city text,
  member_count bigint
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = ''
AS $$
BEGIN
  IF NOT public.has_role_on_account(p_account_id) THEN
    RAISE EXCEPTION 'Access denied';
  END IF;

  RETURN QUERY
  SELECT
    CASE
      WHEN m.postal_code IS NULL OR m.postal_code = '' THEN 'Keine Angabe'
      ELSE left(m.postal_code, 2)
    END AS postal_prefix,
    COALESCE(NULLIF(m.city, ''), 'Keine Angabe') AS city,
    count(*) AS member_count
  FROM public.members m
  WHERE m.account_id = p_account_id
    AND m.status = 'active'
    AND m.is_archived = false
  GROUP BY postal_prefix, m.city
  ORDER BY member_count DESC;
END;
$$;

GRANT EXECUTE ON FUNCTION public.get_member_geographic_distribution(uuid) TO authenticated;

-- 4. Dues collection rates by category
CREATE OR REPLACE FUNCTION public.get_dues_collection_report(p_account_id uuid)
RETURNS TABLE (
  category_name text,
  member_count bigint,
  expected_amount numeric,
  paid_count bigint,
  collection_rate numeric
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = ''
AS $$
BEGIN
  IF NOT public.has_role_on_account(p_account_id) THEN
    RAISE EXCEPTION 'Access denied';
  END IF;

  RETURN QUERY
  SELECT
    COALESCE(dc.name, 'Keine Kategorie') AS category_name,
    count(m.id) AS member_count,
    COALESCE(sum(dc.amount), 0) AS expected_amount,
    count(*) FILTER (WHERE m.dues_paid = true) AS paid_count,
    CASE WHEN count(m.id) > 0
      THEN round((count(*) FILTER (WHERE m.dues_paid = true)::numeric / count(m.id)) * 100, 1)
      ELSE 0
    END AS collection_rate
  FROM public.members m
  LEFT JOIN public.dues_categories dc ON dc.id = m.dues_category_id
  WHERE m.account_id = p_account_id
    AND m.status = 'active'
    AND m.is_archived = false
  GROUP BY dc.name, dc.sort_order
  ORDER BY dc.sort_order NULLS LAST;
END;
$$;

GRANT EXECUTE ON FUNCTION public.get_dues_collection_report(uuid) TO authenticated;

-- 5. Membership duration analysis
CREATE OR REPLACE FUNCTION public.get_membership_duration_analysis(p_account_id uuid)
RETURNS TABLE (
  duration_bucket text,
  member_count bigint,
  percentage numeric
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = ''
AS $$
DECLARE
  v_total bigint;
BEGIN
  IF NOT public.has_role_on_account(p_account_id) THEN
    RAISE EXCEPTION 'Access denied';
  END IF;

  SELECT count(*) INTO v_total
  FROM public.members
  WHERE account_id = p_account_id AND status = 'active' AND is_archived = false;

  RETURN QUERY
  SELECT
    CASE
      WHEN years < 1 THEN 'Unter 1 Jahr'
      WHEN years BETWEEN 1 AND 5 THEN '1-5 Jahre'
      WHEN years BETWEEN 6 AND 10 THEN '6-10 Jahre'
      WHEN years BETWEEN 11 AND 25 THEN '11-25 Jahre'
      WHEN years > 25 THEN 'Über 25 Jahre'
      ELSE 'Unbekannt'
    END AS duration_bucket,
    count(*) AS member_count,
    CASE WHEN v_total > 0
      THEN round((count(*)::numeric / v_total) * 100, 1)
      ELSE 0
    END AS percentage
  FROM (
    SELECT
      CASE WHEN m.entry_date IS NOT NULL
        THEN extract(year FROM age(current_date, m.entry_date))::int
        ELSE NULL
      END AS years
    FROM public.members m
    WHERE m.account_id = p_account_id
      AND m.status = 'active'
      AND m.is_archived = false
  ) sub
  GROUP BY duration_bucket
  ORDER BY
    CASE duration_bucket
      WHEN 'Unter 1 Jahr' THEN 1
      WHEN '1-5 Jahre' THEN 2
      WHEN '6-10 Jahre' THEN 3
      WHEN '11-25 Jahre' THEN 4
      WHEN 'Über 25 Jahre' THEN 5
      ELSE 6
    END;
END;
$$;

GRANT EXECUTE ON FUNCTION public.get_membership_duration_analysis(uuid) TO authenticated;

-- 6. Department distribution
CREATE OR REPLACE FUNCTION public.get_department_distribution(p_account_id uuid)
RETURNS TABLE (
  department_name text,
  member_count bigint,
  percentage numeric
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = ''
AS $$
DECLARE
  v_total bigint;
BEGIN
  IF NOT public.has_role_on_account(p_account_id) THEN
    RAISE EXCEPTION 'Access denied';
  END IF;

  SELECT count(DISTINCT m.id) INTO v_total
  FROM public.members m
  WHERE m.account_id = p_account_id AND m.status = 'active' AND m.is_archived = false;

  RETURN QUERY
  SELECT
    d.name AS department_name,
    count(DISTINCT mda.member_id) AS member_count,
    CASE WHEN v_total > 0
      THEN round((count(DISTINCT mda.member_id)::numeric / v_total) * 100, 1)
      ELSE 0
    END AS percentage
  FROM public.member_departments d
  LEFT JOIN public.member_department_assignments mda ON mda.department_id = d.id
  LEFT JOIN public.members m ON m.id = mda.member_id
    AND m.status = 'active' AND m.is_archived = false
  WHERE d.account_id = p_account_id
  GROUP BY d.name, d.sort_order
  ORDER BY member_count DESC;
END;
$$;

GRANT EXECUTE ON FUNCTION public.get_department_distribution(uuid) TO authenticated;
