<?php

namespace App\Services\Meet;

use App\Models\Invitation;
use App\Models\Member;
use App\Models\Participant;
use App\Models\Recording;
use App\Models\Room;
use App\Models\Session;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

class ReportService
{
    /**
     * @return array<string, mixed>
     */
    public function orgSummary(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $rooms = $this->roomQuery($ownerRef, $organizationId, $branchId)
            ->whereBetween('scheduled_at', [$from, $to]);

        $sessions = Session::owned($ownerRef)
            ->whereHas('room', fn ($q) => $q->where('organization_id', $organizationId)
                ->when($branchId, fn ($q2) => $q2->where('branch_id', $branchId)))
            ->whereBetween('started_at', [$from, $to]);

        $recordings = Recording::owned($ownerRef)
            ->whereHas('session.room', fn ($q) => $q->where('organization_id', $organizationId)
                ->when($branchId, fn ($q2) => $q2->where('branch_id', $branchId)))
            ->whereBetween('created_at', [$from, $to]);

        $totalMinutes = (clone $sessions)->whereNotNull('ended_at')->get()->sum(function (Session $s) {
            return $s->started_at && $s->ended_at ? (int) $s->started_at->diffInMinutes($s->ended_at) : 0;
        });

        return [
            'meetings_scheduled' => (clone $rooms)->count(),
            'meetings_completed' => (clone $rooms)->where('status', 'ended')->count(),
            'meetings_cancelled' => (clone $rooms)->where('status', 'cancelled')->count(),
            'sessions_held' => (clone $sessions)->count(),
            'total_minutes' => $totalMinutes,
            'peak_participants' => (clone $sessions)->max('peak_participants') ?? 0,
            'recordings_count' => (clone $recordings)->count(),
            'recording_storage_bytes' => (int) (clone $recordings)->where('status', 'ready')->sum('file_size'),
            'active_members' => Member::owned($ownerRef)->where('organization_id', $organizationId)->count(),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function meetingsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $rooms = $this->roomQuery($ownerRef, $organizationId, $branchId)
            ->whereBetween('scheduled_at', [$from, $to])
            ->with(['sessions'])
            ->orderBy('scheduled_at')
            ->get();

        $rows = $rooms->map(fn (Room $room) => [
            'title' => $room->title,
            'type' => $room->type,
            'status' => $room->status,
            'scheduled_at' => $room->scheduled_at?->toIso8601String(),
            'duration_minutes' => $room->duration_minutes,
            'sessions' => $room->sessions->count(),
            'peak_participants' => $room->sessions->max('peak_participants') ?? 0,
            'invitations' => $room->invitations()->count(),
            'accepted' => $room->invitations()->where('status', 'accepted')->count(),
        ]);

        return [
            'total' => $rows->count(),
            'by_type' => $rows->groupBy('type')->map->count(),
            'rows' => $rows,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function usersReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $participants = Participant::owned($ownerRef)
            ->whereHas('session.room', fn ($q) => $q->where('organization_id', $organizationId)
                ->when($branchId, fn ($q2) => $q2->where('branch_id', $branchId)))
            ->whereBetween('joined_at', [$from, $to])
            ->get();

        $hosts = Room::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('scheduled_at', [$from, $to])
            ->select('host_user_ref')
            ->distinct()
            ->count('host_user_ref');

        $byUser = $participants->groupBy('user_ref')->map(function (Collection $group, ?string $userRef) {
            $minutes = $group->sum(fn (Participant $p) => $p->joined_at && $p->left_at
                ? (int) $p->joined_at->diffInMinutes($p->left_at)
                : ($p->joined_at ? (int) $p->joined_at->diffInMinutes(now()) : 0));

            return [
                'user_ref' => $userRef,
                'display_name' => $group->first()->display_name,
                'meetings_joined' => $group->count(),
                'minutes' => $minutes,
                'hosted' => $group->where('role', 'host')->count(),
            ];
        })->sortByDesc('minutes')->values();

        return [
            'unique_participants' => $participants->pluck('user_ref')->filter()->unique()->count(),
            'unique_hosts' => $hosts,
            'top_users' => $byUser->take(20),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function invitationsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $invites = Invitation::owned($ownerRef)
            ->whereHas('room', fn ($q) => $q->where('organization_id', $organizationId)
                ->when($branchId, fn ($q2) => $q2->where('branch_id', $branchId)))
            ->whereBetween('created_at', [$from, $to])
            ->get();

        $total = $invites->count();

        return [
            'total' => $total,
            'pending' => $invites->where('status', 'pending')->count(),
            'accepted' => $invites->where('status', 'accepted')->count(),
            'declined' => $invites->where('status', 'declined')->count(),
            'acceptance_rate' => $total > 0 ? round($invites->where('status', 'accepted')->count() / $total * 100, 1) : 0,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function branchRollup(string $ownerRef, int $organizationId, Carbon $from, Carbon $to): array
    {
        return \App\Models\Branch::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->where('is_active', true)
            ->orderBy('name')
            ->get()
            ->map(fn ($branch) => array_merge(
                ['branch_id' => $branch->id, 'branch_name' => $branch->name],
                $this->orgSummary($ownerRef, $organizationId, $from, $to, $branch->id),
            ))
            ->all();
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function toCsv(string $type, array $data): string
    {
        return match ($type) {
            'meetings' => $this->meetingsCsv($data),
            'users' => $this->usersCsv($data),
            'summary' => $this->summaryCsv($data),
            'branches' => $this->branchesCsv($data),
            default => '',
        };
    }

    /** @param  array<string, mixed>  $data */
    protected function meetingsCsv(array $data): string
    {
        $rows = [['Title', 'Type', 'Status', 'Scheduled', 'Duration', 'Sessions', 'Peak', 'Invites', 'Accepted']];
        foreach ($data['rows'] ?? [] as $row) {
            $rows[] = [
                $row['title'], $row['type'], $row['status'], $row['scheduled_at'] ?? '',
                (string) ($row['duration_minutes'] ?? ''), (string) $row['sessions'],
                (string) $row['peak_participants'], (string) $row['invitations'], (string) $row['accepted'],
            ];
        }

        return $this->encodeCsv($rows);
    }

    /** @param  array<string, mixed>  $data */
    protected function usersCsv(array $data): string
    {
        $rows = [['Name', 'User ref', 'Meetings joined', 'Minutes', 'Hosted']];
        foreach ($data['top_users'] ?? [] as $row) {
            $rows[] = [
                $row['display_name'], $row['user_ref'] ?? '', (string) $row['meetings_joined'],
                (string) $row['minutes'], (string) $row['hosted'],
            ];
        }

        return $this->encodeCsv($rows);
    }

    /** @param  array<string, mixed>  $data */
    protected function summaryCsv(array $data): string
    {
        $rows = [['Metric', 'Value']];
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                continue;
            }
            $rows[] = [$key, (string) $value];
        }

        return $this->encodeCsv($rows);
    }

    /** @param  array<int, array<string, mixed>>  $data */
    protected function branchesCsv(array $data): string
    {
        $rows = [['Branch', 'Scheduled', 'Completed', 'Sessions', 'Minutes', 'Recordings']];
        foreach ($data as $row) {
            $rows[] = [
                $row['branch_name'], (string) $row['meetings_scheduled'], (string) $row['meetings_completed'],
                (string) $row['sessions_held'], (string) $row['total_minutes'], (string) $row['recordings_count'],
            ];
        }

        return $this->encodeCsv($rows);
    }

    /** @param  array<int, array<int, string>>  $rows */
    protected function encodeCsv(array $rows): string
    {
        return collect($rows)->map(fn ($r) => implode(',', array_map(
            fn ($c) => '"'.str_replace('"', '""', (string) $c).'"',
            $r,
        )))->implode("\n");
    }

    protected function roomQuery(string $ownerRef, int $organizationId, ?int $branchId)
    {
        return Room::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId));
    }
}
