import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/ae_api/api';
import {
    process_ae_obj__activity_log_props,
    properties_to_save
} from '$lib/ae_core/ae_core__activity_log';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
import { db_core } from '$lib/ae_core/db_core';

interface MeetingEvent {
    timestamp: string;
    action: string;
    details: { full_name?: string };
}

interface MeetingParticipant {
    displayName: string;
    role: string;
    novi_uuid?: string;
}

interface MeetingReport {
    meeting_id: string;
    room_name: string;
    start_time: string;
    final_duration: string;
    final_participant_count: number;
    final_participants: MeetingParticipant[];
    events: MeetingEvent[];
}

const JITSI_REPORT_PAGE_SIZE = 1000;

// MariaDB TEXT columns come back as JSON strings from the API — parse safely.
function safe_parse_meta(raw: unknown): Record<string, unknown> {
    if (!raw) return {};
    if (typeof raw === 'object') return raw as Record<string, unknown>;
    try {
        return JSON.parse(raw as string) as Record<string, unknown>;
    } catch {
        return {};
    }
}

function parse_duration_seconds(d: string): number {
    if (!d || !d.includes(':')) return 0;
    const [h, m, s] = d.split(':').map(Number);
    return (h || 0) * 3600 + (m || 0) * 60 + (s || 0);
}

function normalize_text(value: unknown): string {
    return typeof value === 'string' ? value.trim() : '';
}

function normalize_uuid(value: unknown): string {
    return normalize_text(value).toLowerCase();
}

function extract_uuid_from_url_params(raw: unknown): string {
    if (typeof raw !== 'string' || !raw.trim()) return '';
    try {
        const params = new URLSearchParams(raw);
        return normalize_uuid(params.get('uuid'));
    } catch {
        return '';
    }
}

function extract_participant_uuid(source: Record<string, unknown>): string {
    const nested_user = source.user as Record<string, unknown> | undefined;
    const nested_context = source.context as Record<string, unknown> | undefined;
    const nested_context_user = nested_context?.user as
        | Record<string, unknown>
        | undefined;

    const candidates = [
        source.novi_uuid,
        source.uuid,
        source.novi_customer_uid,
        nested_user?.novi_uuid,
        nested_user?.uuid,
        nested_context_user?.novi_uuid,
        nested_context_user?.uuid
    ];

    for (const candidate of candidates) {
        const uuid = normalize_uuid(candidate);
        if (uuid) return uuid;
    }

    return '';
}

function extract_flat_search_results(result: unknown): any[] {
    if (Array.isArray(result)) return result;
    if (
        result &&
        typeof result === 'object' &&
        Array.isArray((result as { data?: unknown }).data)
    ) {
        return (result as { data: any[] }).data;
    }
    return [];
}

/**
 * @description Queries all Jitsi-related activity logs and processes them into a structured report,
 * grouped by meeting ID.
 * @param api_cfg The API configuration object.
 * @param account_id The account ID to query against.
 * @param enabled Filter by enabled status.
 * @param hidden Filter by hidden status.
 * @param limit Maximum number of logs to fetch.
 * @param try_cache Whether to sync the raw logs to the local activity_log table.
 * @param log_lvl The logging level.
 * @returns A structured array of meeting report objects.
 */
function build_jitsi_report_from_logs(
    flat_log_list: any[],
    log_lvl = 0
): MeetingReport[] {
    // Participants come from two sources — both are needed for a complete list:
    //   1. jitsi_meeting_participant_joined events: meta.full_name (all who ever joined)
    //   2. jitsi_meeting_init / stats / end snapshots: meta.participants[].displayName+role
    // Source 2 has role info; source 1 may catch participants who left before the snapshot.
    // We merge both into a Map<displayName, participant> per meeting, keeping the
    // stable display-name key used by the existing report while preserving Novi UUIDs
    // when the log payload includes them.
    //
    // Duration: take the MAX across all init/stats/end events — periodic stats may
    // have a higher value than the final init summary in some Jitsi configurations.

    const meetings = new Map<string, MeetingReport>();
    const participant_maps = new Map<string, Map<string, MeetingParticipant>>();
    const max_duration_secs = new Map<string, number>();

    for (const log of flat_log_list) {
        const meeting_id = log.external_client_id;
        if (!meeting_id) continue;
        if (!log.name?.startsWith('jitsi_')) continue;

        if (!meetings.has(meeting_id)) {
            meetings.set(meeting_id, {
                meeting_id,
                room_name: 'Unknown',
                start_time: log.created_on,
                final_duration: '00:00:00',
                final_participants: [],
                final_participant_count: 0,
                events: []
            });
            participant_maps.set(meeting_id, new Map());
            max_duration_secs.set(meeting_id, 0);
        }

        const meeting_report = meetings.get(meeting_id)!;
        const p_map = participant_maps.get(meeting_id)!;
        const meta = safe_parse_meta(log.meta_json);
        const log_novi_uuid = extract_uuid_from_url_params(log.url_params);

        if (log.action === 'jitsi_meeting_init') {
            // Strip "Event in room: " prefix Jitsi sometimes prepends to the description
            meeting_report.room_name =
                (log.description ?? '').replace(/^Event in room:\s*/i, '').trim() ||
                'Unknown';
            meeting_report.start_time = log.created_on;
        }

        // Parse duration from init, stats, or end — keep the maximum seen
        if (
            log.action === 'jitsi_meeting_init' ||
            log.action === 'jitsi_meeting_stats' ||
            log.action === 'jitsi_meeting_end'
        ) {
            const dur_str = ((meta.duration ?? meta.final_duration) as string) || '';
            if (dur_str) {
                const secs = parse_duration_seconds(dur_str);
                if (secs > (max_duration_secs.get(meeting_id) ?? 0)) {
                    max_duration_secs.set(meeting_id, secs);
                    meeting_report.final_duration = dur_str;
                }
            }

            // Merge snapshot participant list (has role info — preferred source)
            const snapshot = meta.participants as
                | Array<Record<string, unknown>>
                | undefined;
            if (Array.isArray(snapshot)) {
                for (const p of snapshot) {
                    const display_name = normalize_text(p.displayName);
                    if (!display_name) continue;

                    const role =
                        typeof p.role === 'string' ? p.role : 'participant';
                    const novi_uuid =
                        extract_participant_uuid(p) ||
                        (p.role === 'moderator'
                            ? normalize_uuid(meta.moderator_novi_uuid)
                            : '');
                    const existing_participant = p_map.get(display_name);

                    if (!existing_participant) {
                        p_map.set(display_name, {
                            displayName: display_name,
                            role,
                            ...(novi_uuid ? { novi_uuid } : {})
                        });
                        continue;
                    }

                    if (
                        existing_participant.role !== 'moderator' &&
                        role === 'moderator'
                    ) {
                        existing_participant.role = 'moderator';
                    }
                    if (!existing_participant.novi_uuid && novi_uuid) {
                        existing_participant.novi_uuid = novi_uuid;
                    }
                }
            }
        }

        // Collect participants from join events (may catch people who left before the snapshot)
        if (log.action === 'jitsi_meeting_participant_joined') {
            const display_name = normalize_text(meta.full_name);
            if (display_name) {
                const role =
                    typeof meta.role === 'string' ? meta.role : 'participant';
                const novi_uuid = log_novi_uuid || extract_participant_uuid(meta);
                const existing_participant = p_map.get(display_name);

                if (!existing_participant) {
                    p_map.set(display_name, {
                        displayName: display_name,
                        role,
                        ...(novi_uuid ? { novi_uuid } : {})
                    });
                } else {
                    if (
                        existing_participant.role !== 'moderator' &&
                        role === 'moderator'
                    ) {
                        existing_participant.role = 'moderator';
                    }
                    if (!existing_participant.novi_uuid && novi_uuid) {
                        existing_participant.novi_uuid = novi_uuid;
                    }
                }
            }
        }

        // Discrete events for the timeline (all non-init actions)
        if (log.action !== 'jitsi_meeting_init') {
            meeting_report.events.push({
                timestamp: log.created_on,
                action: log.action,
                details: {
                    full_name: (meta.full_name as string) ?? undefined
                }
            });
        }
    }

    // Compile final participant lists from the deduplicated maps
    for (const [meeting_id, p_map] of participant_maps) {
        const meeting_report = meetings.get(meeting_id);
        if (!meeting_report) continue;
        if (p_map.size > 0) {
            // Sort: moderators first, then alphabetically
            meeting_report.final_participants = Array.from(p_map.values()).sort(
                (a: MeetingParticipant, b: MeetingParticipant) => {
                    if (a.role === 'moderator' && b.role !== 'moderator') return -1;
                    if (a.role !== 'moderator' && b.role === 'moderator') return 1;
                    return a.displayName.localeCompare(b.displayName);
                }
            );
            meeting_report.final_participant_count = p_map.size;
        }
    }

    // Sort events within each meeting chronologically
    for (const report of meetings.values()) {
        report.events.sort(
            (a: MeetingEvent, b: MeetingEvent) =>
                new Date(a.timestamp).getTime() -
                new Date(b.timestamp).getTime()
        );
    }

    const final_report = Array.from(meetings.values());
    final_report.sort(
        (a, b) =>
            new Date(b.start_time).getTime() - new Date(a.start_time).getTime()
    );

    if (log_lvl) console.log('Final Jitsi report:', final_report);

    return final_report;
}

export async function load_jitsi_report_from_cache({
    account_id,
    log_lvl = 0
}: {
    account_id: string;
    log_lvl?: number;
}): Promise<MeetingReport[] | null> {
    try {
        const cached_logs = await db_core.activity_log
            .where('account_id_random')
            .equals(account_id)
            .and(
                (log) =>
                    log.name === 'jitsi_meeting_event' ||
                    log.name === 'jitsi_meeting_stats'
            )
            .sortBy('created_on');

        if (cached_logs.length === 0) return null;
        if (
            cached_logs.some(
                (log) => typeof log.url_params !== 'string' || !log.url_params
            )
        ) {
            if (log_lvl) {
                console.log(
                    'Jitsi report cache is missing url_params; using API refresh for accurate UUID filtering.'
                );
            }
            return null;
        }
        if (log_lvl) {
            console.log(
                `Jitsi report cache hit: ${cached_logs.length} activity_log rows`
            );
        }
        return build_jitsi_report_from_logs(cached_logs.reverse(), log_lvl);
    } catch (err) {
        if (log_lvl) console.warn('Jitsi report cache read failed.', err);
        return null;
    }
}

export async function qry__jitsi_report({
    api_cfg,
    account_id,
    enabled = 'all',
    hidden = 'all',
    limit = JITSI_REPORT_PAGE_SIZE,
    try_cache = true,
    log_lvl = 0
}: {
    api_cfg: any;
    account_id: string;
    enabled?: 'enabled' | 'all' | 'not_enabled' | undefined;
    hidden?: 'hidden' | 'all' | 'not_hidden' | undefined;
    limit?: number;
    try_cache?: boolean;
    log_lvl?: number;
}) {
    if (log_lvl) console.log('*** qry__jitsi_report() ***');

    // Step 1: Query all relevant activity logs from the API.
    const search_query = {
        or: [
            { field: 'name', op: 'eq', value: 'jitsi_meeting_event' },
            { field: 'name', op: 'eq', value: 'jitsi_meeting_stats' }
        ],
        and: [{ field: 'account_id_random', op: 'eq', value: account_id }]
    };

    const flat_log_list: any[] = [];
    const order_by_li: Record<string, 'ASC' | 'DESC'> = {
        created_on: 'DESC'
    };
    let offset = 0;

    while (true) {
        const result = await api.search_ae_obj({
            api_cfg: api_cfg,
            obj_type: 'activity_log',
            search_query,
            headers: { 'x-account-id': account_id },
            enabled,
            hidden,
            order_by_li,
            limit,
            offset,
            log_lvl: log_lvl
        });

        const page = extract_flat_search_results(result);
        if (page.length === 0) break;

        flat_log_list.push(...page);
        if (page.length < limit) break;
        offset += limit;
    }

    if (
        !flat_log_list ||
        !Array.isArray(flat_log_list) ||
        flat_log_list.length === 0
    ) {
        if (log_lvl)
            console.log('No Jitsi activity logs found or error occurred.');
        return [];
    }

    // Frontier Standard: Process and Save to local cache
    if (try_cache) {
        const processed_obj_li = await process_ae_obj__activity_log_props({
            obj_li: flat_log_list,
            log_lvl: log_lvl
        });
        await db_save_ae_obj_li__ae_obj({
            db_instance: db_core,
            table_name: 'activity_log',
            obj_li: processed_obj_li,
            properties_to_save: properties_to_save,
            log_lvl: log_lvl
        });
    }

    return build_jitsi_report_from_logs(flat_log_list, log_lvl);
}

export const load_jitsi_report = qry__jitsi_report;
