-- =============================================================
-- Reports Infrastructure: pg_trgm + Server-side RPC functions
-- =============================================================
-- All RPCs accept a date range (p_start, p_end) and use
-- SECURITY INVOKER so existing RLS policies apply.
-- =============================================================

-- Enable pg_trgm for fuzzy subject clustering
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- GIN trigram indexes for subject similarity searches
CREATE INDEX IF NOT EXISTS idx_tickets_subject_trgm
  ON tickets USING gin (lower(subject) gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_tasks_title_trgm
  ON tasks USING gin (lower(title) gin_trgm_ops);

-- ----- 1. Tickets by status -----
CREATE OR REPLACE FUNCTION report_tickets_by_status(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(status text, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT t.status, count(*)
  FROM tickets t
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY t.status
  ORDER BY count(*) DESC;
$$;

-- ----- 2. Tasks by status -----
CREATE OR REPLACE FUNCTION report_tasks_by_status(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(status text, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT t.status::text, count(*)
  FROM tasks t
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY t.status
  ORDER BY count(*) DESC;
$$;

-- ----- 3. Tasks created by hour of day -----
CREATE OR REPLACE FUNCTION report_tasks_by_hour(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(hour int, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT EXTRACT(HOUR FROM t.created_at AT TIME ZONE 'Asia/Manila')::int AS hour,
         count(*)
  FROM tasks t
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY hour
  ORDER BY hour;
$$;

-- ----- 4. Tickets created by hour of day -----
CREATE OR REPLACE FUNCTION report_tickets_by_hour(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(hour int, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT EXTRACT(HOUR FROM t.created_at AT TIME ZONE 'Asia/Manila')::int AS hour,
         count(*)
  FROM tickets t
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY hour
  ORDER BY hour;
$$;

-- ----- 5. Top offices by ticket count -----
CREATE OR REPLACE FUNCTION report_top_offices_tickets(
  p_start  timestamptz,
  p_end    timestamptz,
  p_limit  int DEFAULT 10
)
RETURNS TABLE(office_name text, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT o.name AS office_name, count(*)
  FROM tickets t
  JOIN offices o ON o.id = t.office_id
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY o.name
  ORDER BY count(*) DESC
  LIMIT p_limit;
$$;

-- ----- 6. Top offices by task count -----
CREATE OR REPLACE FUNCTION report_top_offices_tasks(
  p_start  timestamptz,
  p_end    timestamptz,
  p_limit  int DEFAULT 10
)
RETURNS TABLE(office_name text, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT o.name AS office_name, count(*)
  FROM tasks t
  JOIN offices o ON o.id = t.office_id
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY o.name
  ORDER BY count(*) DESC
  LIMIT p_limit;
$$;

-- ----- 7. Top ticket subjects (pg_trgm clustering) -----
CREATE OR REPLACE FUNCTION report_top_ticket_subjects(
  p_start     timestamptz,
  p_end       timestamptz,
  p_limit     int   DEFAULT 10,
  p_threshold float DEFAULT 0.4
)
RETURNS TABLE(subject_group text, ticket_count bigint)
LANGUAGE plpgsql STABLE SECURITY INVOKER
AS $$
DECLARE
  rec RECORD;
  cluster_leaders text[] := '{}';
  cluster_counts  bigint[] := '{}';
  i int;
  matched boolean;
  best_idx int;
  best_sim float;
  cur_sim  float;
BEGIN
  -- Iterate subjects ordered by frequency (greedy clustering)
  FOR rec IN
    SELECT lower(trim(t.subject)) AS norm_subject, count(*) AS cnt
    FROM tickets t
    WHERE t.created_at >= p_start
      AND t.created_at <  p_end
      AND t.subject IS NOT NULL
      AND trim(t.subject) <> ''
    GROUP BY norm_subject
    ORDER BY cnt DESC
  LOOP
    matched := false;
    best_idx := 0;
    best_sim := 0;

    FOR i IN 1..coalesce(array_length(cluster_leaders, 1), 0) LOOP
      cur_sim := similarity(rec.norm_subject, cluster_leaders[i]);
      IF cur_sim >= p_threshold AND cur_sim > best_sim THEN
        best_sim := cur_sim;
        best_idx := i;
        matched := true;
      END IF;
    END LOOP;

    IF matched THEN
      cluster_counts[best_idx] := cluster_counts[best_idx] + rec.cnt;
    ELSE
      cluster_leaders := array_append(cluster_leaders, rec.norm_subject);
      cluster_counts  := array_append(cluster_counts, rec.cnt);
    END IF;
  END LOOP;

  -- Return top clusters
  RETURN QUERY
    SELECT sub.cl AS subject_group, sub.cc AS ticket_count
    FROM (
      SELECT unnest(cluster_leaders) AS cl,
             unnest(cluster_counts) AS cc
    ) sub
    ORDER BY sub.cc DESC
    LIMIT p_limit;
END;
$$;

-- ----- 8. Top task subjects (pg_trgm clustering) -----
CREATE OR REPLACE FUNCTION report_top_task_subjects(
  p_start     timestamptz,
  p_end       timestamptz,
  p_limit     int   DEFAULT 10,
  p_threshold float DEFAULT 0.4
)
RETURNS TABLE(subject_group text, task_count bigint)
LANGUAGE plpgsql STABLE SECURITY INVOKER
AS $$
DECLARE
  rec RECORD;
  cluster_leaders text[] := '{}';
  cluster_counts  bigint[] := '{}';
  i int;
  matched boolean;
  best_idx int;
  best_sim float;
  cur_sim  float;
BEGIN
  FOR rec IN
    SELECT lower(trim(t.title)) AS norm_title, count(*) AS cnt
    FROM tasks t
    WHERE t.created_at >= p_start
      AND t.created_at <  p_end
      AND t.title IS NOT NULL
      AND trim(t.title) <> ''
    GROUP BY norm_title
    ORDER BY cnt DESC
  LOOP
    matched := false;
    best_idx := 0;
    best_sim := 0;

    FOR i IN 1..coalesce(array_length(cluster_leaders, 1), 0) LOOP
      cur_sim := similarity(rec.norm_title, cluster_leaders[i]);
      IF cur_sim >= p_threshold AND cur_sim > best_sim THEN
        best_sim := cur_sim;
        best_idx := i;
        matched := true;
      END IF;
    END LOOP;

    IF matched THEN
      cluster_counts[best_idx] := cluster_counts[best_idx] + rec.cnt;
    ELSE
      cluster_leaders := array_append(cluster_leaders, rec.norm_title);
      cluster_counts  := array_append(cluster_counts, rec.cnt);
    END IF;
  END LOOP;

  RETURN QUERY
    SELECT sub.cl AS subject_group, sub.cc AS task_count
    FROM (
      SELECT unnest(cluster_leaders) AS cl,
             unnest(cluster_counts) AS cc
    ) sub
    ORDER BY sub.cc DESC
    LIMIT p_limit;
END;
$$;

-- ----- 9. Monthly overview (tickets + tasks) -----
CREATE OR REPLACE FUNCTION report_monthly_overview(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(month text, ticket_count bigint, task_count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  WITH months AS (
    SELECT to_char(date_trunc('month', d), 'YYYY-MM') AS m
    FROM generate_series(
      date_trunc('month', p_start),
      date_trunc('month', p_end - interval '1 day'),
      interval '1 month'
    ) d
  ),
  tk AS (
    SELECT to_char(date_trunc('month', created_at), 'YYYY-MM') AS m,
           count(*) AS cnt
    FROM tickets
    WHERE created_at >= p_start AND created_at < p_end
    GROUP BY m
  ),
  ta AS (
    SELECT to_char(date_trunc('month', created_at), 'YYYY-MM') AS m,
           count(*) AS cnt
    FROM tasks
    WHERE created_at >= p_start AND created_at < p_end
    GROUP BY m
  )
  SELECT months.m AS month,
         coalesce(tk.cnt, 0) AS ticket_count,
         coalesce(ta.cnt, 0) AS task_count
  FROM months
  LEFT JOIN tk ON tk.m = months.m
  LEFT JOIN ta ON ta.m = months.m
  ORDER BY months.m;
$$;

-- ----- 10. Request type distribution -----
CREATE OR REPLACE FUNCTION report_request_type_distribution(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(request_type text, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT coalesce(t.request_type::text, 'Unspecified') AS request_type,
         count(*)
  FROM tasks t
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY request_type
  ORDER BY count(*) DESC;
$$;

-- ----- 11. Request category distribution -----
CREATE OR REPLACE FUNCTION report_request_category_distribution(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(request_category text, count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT coalesce(t.request_category::text, 'Unspecified') AS request_category,
         count(*)
  FROM tasks t
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
  GROUP BY request_category
  ORDER BY count(*) DESC;
$$;

-- ----- 12. Avg resolution time by office -----
CREATE OR REPLACE FUNCTION report_avg_resolution_by_office(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(office_name text, avg_hours float)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT o.name AS office_name,
         avg(EXTRACT(EPOCH FROM (t.completed_at - t.created_at)) / 3600.0)::float AS avg_hours
  FROM tasks t
  JOIN offices o ON o.id = t.office_id
  WHERE t.created_at >= p_start
    AND t.created_at <  p_end
    AND t.completed_at IS NOT NULL
    AND t.status::text = 'completed'
  GROUP BY o.name
  ORDER BY avg_hours DESC;
$$;

-- ----- 13. Staff workload (assigned vs completed) -----
CREATE OR REPLACE FUNCTION report_staff_workload(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(staff_name text, assigned_count bigint, completed_count bigint)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT p.full_name AS staff_name,
         count(DISTINCT ta.task_id) AS assigned_count,
         count(DISTINCT CASE WHEN t.status::text = 'completed'
                              AND t.completed_at >= p_start
                              AND t.completed_at <  p_end
                         THEN t.id END) AS completed_count
  FROM task_assignments ta
  JOIN profiles p ON p.id = ta.user_id
  JOIN tasks t ON t.id = ta.task_id
  WHERE ta.created_at >= p_start
    AND ta.created_at <  p_end
  GROUP BY p.full_name
  ORDER BY assigned_count DESC;
$$;

-- ----- 14. Ticket-to-task conversion rate -----
CREATE OR REPLACE FUNCTION report_ticket_to_task_rate(
  p_start timestamptz,
  p_end   timestamptz
)
RETURNS TABLE(total_tickets bigint, promoted_tickets bigint, conversion_rate float)
LANGUAGE sql STABLE SECURITY INVOKER
AS $$
  SELECT
    count(*)::bigint AS total_tickets,
    count(*) FILTER (WHERE promoted_at IS NOT NULL)::bigint AS promoted_tickets,
    CASE WHEN count(*) > 0
      THEN (count(*) FILTER (WHERE promoted_at IS NOT NULL)::float / count(*)::float * 100)
      ELSE 0
    END AS conversion_rate
  FROM tickets
  WHERE created_at >= p_start
    AND created_at <  p_end;
$$;
