<?php

namespace App\Http\Controllers\Api\Mobile;

use App\Http\Controllers\Controller;
use App\Models\ChannelIntegration;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

/**
 * Mobile-app facing channel connection management.
 *
 * The Flutter app can connect / update / disconnect WhatsApp, Twilio SMS,
 * Slack and Telegram from inside the app. Credential shape and validation
 * mirror Settings\ChannelSettings.php exactly so a web user and a mobile
 * user see the same fields — the model handles encryption transparently.
 *
 * Secrets are NEVER returned in responses. The list endpoint masks every
 * credential field as "••••" + last 4 chars when present.
 */
class ChannelsController extends Controller
{
    /**
     * Per-channel credential schema mirrored from ChannelSettings::save().
     * The keys define both the validation contract AND the canonical
     * field set persisted on the channel_integrations row.
     */
    private const CHANNEL_SCHEMA = [
        'whatsapp' => [
            'phone_number_id' => 'required|string|max:128',
            'access_token'    => 'required|string|max:512',
            'verify_token'    => 'nullable|string|max:128',
            'app_secret'      => 'nullable|string|max:128',
        ],
        'sms' => [
            'sid'          => 'required|string|max:128',
            'auth_token'   => 'required|string|max:256',
            'phone_number' => 'required|string|max:32',
        ],
        'telegram' => [
            'bot_token' => 'required|string|max:256',
        ],
        // `bot_token` is required so the SlackIntegrationService can actually
        // call Slack's API (conversations.list, chat.postMessage). Web gets
        // it from the OAuth callback; mobile gets it either by:
        //   (a) the Flutter app driving Slack OAuth inside a webview and
        //       posting the resulting xoxb- token here, or
        //   (b) the user manually pasting the bot token from a Slack app
        //       they created themselves at api.slack.com/apps.
        // Without this, the channel-list picker would return empty and
        // outbound replies would fail silently.
        'slack' => [
            'client_id'      => 'required|string|max:128',
            'client_secret'  => 'required|string|max:128',
            'signing_secret' => 'required|string|max:128',
            'bot_token'      => 'required|string|max:512|starts_with:xoxb-,xoxp-,xoxo-',
            'team_id'        => 'nullable|string|max:32',
            'team_name'      => 'nullable|string|max:128',
        ],
    ];

    /**
     * GET /api/v1/mobile/channels
     *
     * List the workspace's channel connections with status + masked creds.
     * Always returns a row per known channel so the app can render an
     * "Add WhatsApp" CTA even when no integration exists yet.
     */
    public function index(Request $request): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;

        $existing = ChannelIntegration::where('workspace_id', $workspaceId)
            ->whereIn('channel', array_keys(self::CHANNEL_SCHEMA))
            ->get()
            ->keyBy('channel');

        $out = [];
        foreach (self::CHANNEL_SCHEMA as $channel => $schema) {
            $row = $existing->get($channel);
            $creds = $row?->credentials ?? [];

            $out[] = [
                'channel'        => $channel,
                'status'         => $row?->status ?? 'not_configured',
                'connected'      => $row !== null && ($row->status === 'active'),
                'phone_number'   => $row?->phone_number,
                'account_name'   => $row?->account_name,
                'error_message'  => $row?->error_message,
                'masked_fields'  => $this->maskCredentials($creds),
                'updated_at'     => $row?->updated_at?->toIso8601String(),
            ];
        }

        return response()->json([
            'success' => true,
            'data'    => $out,
        ]);
    }

    /**
     * POST /api/v1/mobile/channels/{channel}
     *
     * Connect a new channel. If a row already exists for this workspace +
     * channel, this call replaces its credentials (idempotent connect).
     */
    public function store(Request $request, string $channel): JsonResponse
    {
        return $this->upsert($request, $channel);
    }

    /**
     * PUT /api/v1/mobile/channels/{channel}
     *
     * Update credentials on an already-connected channel. Behaviorally
     * identical to store() because the model uses updateOrCreate semantics
     * keyed on (workspace_id, channel) — but kept as a separate route so
     * the API surface reads cleanly.
     */
    public function update(Request $request, string $channel): JsonResponse
    {
        return $this->upsert($request, $channel);
    }

    /**
     * DELETE /api/v1/mobile/channels/{channel}
     *
     * Disconnect the channel. Soft-deletes the row (the model uses
     * SoftDeletes) so historic webhook traffic still has a reference.
     */
    public function destroy(Request $request, string $channel): JsonResponse
    {
        if (!isset(self::CHANNEL_SCHEMA[$channel])) {
            return response()->json([
                'success' => false,
                'message' => 'Unknown channel.',
            ], 404);
        }

        $row = ChannelIntegration::where('workspace_id', $request->user()->active_workspace_id)
            ->where('channel', $channel)
            ->first();

        if (!$row) {
            return response()->json([
                'success' => true,
                'message' => 'Already disconnected.',
            ]);
        }

        $row->delete();

        return response()->json([
            'success' => true,
            'message' => ucfirst($channel) . ' disconnected.',
        ]);
    }

    /**
     * GET /api/v1/mobile/channels/slack/conversations
     *
     * After a Slack workspace is connected, the mobile UI needs to show the
     * user the list of Slack channels their bot can see, so they can pick
     * which channel MailTrixy uses for notifications and outbound replies.
     * This is the exact parity step that the web Integrations modal does
     * (Settings → Integrations → Slack → "Select Channel").
     *
     * Returns:
     *   { success: true, data: [{ id, name, is_private, num_members }, …],
     *     meta: { selected_channel_id, total } }
     *
     * Behaves cleanly when Slack isn't connected yet — returns 412 so the
     * mobile app can show "Connect Slack first" without parsing a generic
     * empty list as success.
     */
    public function slackConversations(Request $request): JsonResponse
    {
        $workspaceId = (int) $request->user()->active_workspace_id;

        $integration = ChannelIntegration::where('workspace_id', $workspaceId)
            ->where('channel', 'slack')
            ->first();

        if (! $integration) {
            return response()->json([
                'success'    => false,
                'error_code' => 'slack_not_connected',
                'message'    => 'Connect Slack first before listing channels.',
            ], 412);
        }

        try {
            $channels = app(\App\Services\Integrations\SlackIntegrationService::class)
                ->syncChannels($workspaceId);
        } catch (\Throwable $e) {
            \Illuminate\Support\Facades\Log::error('Mobile Slack channel sync failed', [
                'workspace_id' => $workspaceId,
                'error'        => $e->getMessage(),
            ]);
            return response()->json([
                'success'    => false,
                'error_code' => 'slack_api_error',
                'message'    => 'Slack channel list could not be fetched: ' . $e->getMessage(),
            ], 502);
        }

        return response()->json([
            'success' => true,
            'data'    => array_map(fn ($c) => [
                'id'          => (string) ($c['id'] ?? ''),
                'name'        => (string) ($c['name'] ?? ''),
                'is_private'  => (bool)   ($c['is_private'] ?? false),
                'num_members' => (int)    ($c['num_members'] ?? 0),
            ], $channels),
            'meta'    => [
                'selected_channel_id' => $integration->slack_channel_id,
                'total'               => count($channels),
            ],
        ]);
    }

    /**
     * PUT /api/v1/mobile/channels/slack/selected-channel
     *
     * Saves the Slack channel id the user picked from the list returned by
     * `GET …/slack/conversations`. Mirrors the web "Save" action on the
     * Integrations channel-picker modal — once this is set, inbound Slack
     * messages route into MailTrixy and outbound replies route back to the
     * right Slack channel via `channel_conversation_id = slack_channel_id`.
     *
     * Body: { channel_id: "C01ABC…" }
     */
    public function slackSelectChannel(Request $request): JsonResponse
    {
        $workspaceId = (int) $request->user()->active_workspace_id;

        $data = Validator::make($request->all(), [
            // Slack channel ids look like C01ABC…, G01ABC… (legacy private),
            // or D01ABC… (DM). Letters + digits, typically 9-32 chars.
            'channel_id' => ['required', 'string', 'max:32', 'regex:/^[A-Z][A-Z0-9]{2,31}$/'],
        ])->validate();

        $ok = app(\App\Services\Integrations\SlackIntegrationService::class)
            ->setNotificationChannel($workspaceId, $data['channel_id']);

        if (! $ok) {
            return response()->json([
                'success'    => false,
                'error_code' => 'slack_not_connected',
                'message'    => 'Connect Slack first before selecting a channel.',
            ], 412);
        }

        return response()->json([
            'success' => true,
            'message' => 'Slack channel updated.',
            'data'    => [
                'selected_channel_id' => $data['channel_id'],
            ],
        ]);
    }

    // ----------------------------------------------------------------
    //  Internals
    // ----------------------------------------------------------------

    private function upsert(Request $request, string $channel): JsonResponse
    {
        $schema = self::CHANNEL_SCHEMA[$channel] ?? null;
        if (!$schema) {
            return response()->json([
                'success' => false,
                'message' => 'Unknown channel.',
            ], 404);
        }

        // Plan-feature gate mirrors Settings\ChannelSettings line 122-128.
        $featureMap = [
            'whatsapp' => 'whatsapp',
            'sms'      => 'twilio',
            'telegram' => 'telegram',
            'slack'    => 'slack',
        ];
        $featureKey = $featureMap[$channel] ?? null;
        if ($featureKey) {
            $workspace = $request->user()->activeWorkspace;
            $planService = app(\App\Services\PlanLimitService::class);
            if (!$planService->hasFeature($workspace, $featureKey)) {
                return response()->json([
                    'success' => false,
                    'message' => ucfirst($channel) . ' is not available on your current plan.',
                ], 403);
            }
        }

        $validator = Validator::make($request->all(), $schema);
        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'errors'  => $validator->errors(),
            ], 422);
        }

        $credentials = collect($schema)
            ->mapWithKeys(fn ($_, $field) => [$field => $request->input($field)])
            ->all();

        $topLevel = [
            'credentials'  => $credentials,
            'config'       => [],
            'status'       => 'active',
            'ai_auto_reply' => false,
        ];
        if ($channel === 'sms') {
            $topLevel['phone_number'] = $request->input('phone_number');
            $topLevel['account_name'] = 'Twilio';
        } elseif ($channel === 'whatsapp') {
            $topLevel['phone_number'] = $request->input('phone_number_id');
        } elseif ($channel === 'slack') {
            // Mirror what the web OAuth callback (IntegrationOAuthController::
            // slackCallback) writes: top-level slack_bot_token + slack_team_id
            // columns + credentials['access_token']. The rest of the system
            // (SlackIntegrationService::getBotToken, SlackService::postMessage,
            // inbound webhook handler) reads from these locations, so
            // populating them keeps the mobile-connected install at FULL
            // PARITY with the web-OAuth-connected install.
            $botToken = (string) $request->input('bot_token');
            $teamId   = (string) ($request->input('team_id') ?: '');
            $teamName = (string) ($request->input('team_name') ?: '');

            $credentials['access_token'] = $botToken;          // SlackIntegrationService::getBotToken() looks here first
            if ($teamId !== '')   $credentials['team_id']   = $teamId;
            if ($teamName !== '') $credentials['team_name'] = $teamName;
            $topLevel['credentials']     = $credentials;
            $topLevel['slack_bot_token'] = $botToken;
            if ($teamId !== '')   $topLevel['slack_team_id'] = $teamId;
            if ($teamName !== '') $topLevel['account_name']  = $teamName;
        }

        ChannelIntegration::updateOrCreate(
            [
                'workspace_id' => $request->user()->active_workspace_id,
                'channel'      => $channel,
            ],
            $topLevel
        );

        return response()->json([
            'success' => true,
            'message' => ucfirst($channel) . ' connected.',
        ]);
    }

    /**
     * Replace every credential value with "••••" + last 4 chars so the
     * mobile UI can show "you have a saved token" without leaking secrets.
     */
    private function maskCredentials(array $creds): array
    {
        $masked = [];
        foreach ($creds as $k => $v) {
            $v = (string) ($v ?? '');
            if ($v === '') {
                $masked[$k] = null;
                continue;
            }
            $masked[$k] = strlen($v) <= 4 ? '••••' : '••••' . substr($v, -4);
        }
        return $masked;
    }
}
