// PUT  /api/me/channels/[type] — upsert a personal channel for the calling user
// DELETE /api/me/channels/[type] — remove the calling user's channel of that type
//
// CHAN-02, CHAN-03, CHAN-04, CHAN-05, CHAN-07 / D-02..D-06
// Auth: requireAuth(). Write target is always session.user.id.
// T-09-02-01: no userId/user_id accepted from body or query.
// T-09-02-06: Teams URLs validated via isValidTeamsWebhookUrl (SSRF mitigation).
// T-09-02-07: ntfy topics minted server-side; custom topics validated.
// T-09-02-10: partial unique index closes race window; 23505 → 409.

import { NextRequest, NextResponse } from 'next/server';
import { requireAuth } from '@/lib/auth-utils';
import { postgresClient } from '@/lib/services/postgres-client';
import { NotificationChannel } from '@/lib/types/pipeline';
import {
  isPersonalChannelType,
  isValidTeamsWebhookUrl,
  isValidNtfyTopic,
  mintNtfyTopic,
  sendChannelTest,
} from '@/lib/services/personal-channels';

type RouteParams = { params: Promise<{ type: string }> };

export async function PUT(request: NextRequest, { params }: RouteParams): Promise<NextResponse> {
  const { session, error } = await requireAuth();
  if (error) return error;

  const { type } = await params;
  if (!isPersonalChannelType(type)) {
    return NextResponse.json(
      { error: 'Unsupported channel type', message: `type must be one of: teams, ntfy` },
      { status: 400 },
    );
  }

  let body: Record<string, unknown>;
  try {
    body = await request.json();
  } catch {
    return NextResponse.json(
      { error: 'Invalid JSON', message: 'Request body must be JSON' },
      { status: 400 },
    );
  }

  let name: string;
  let config: Record<string, unknown>;

  if (type === 'teams') {
    const webhook_url = body.webhook_url;
    if (!isValidTeamsWebhookUrl(webhook_url)) {
      return NextResponse.json(
        {
          error: 'Invalid webhook URL',
          message: 'webhook_url must be https://*.webhook.office.com or https://*.logic.azure.com',
        },
        { status: 400 },
      );
    }
    config = { webhook_url };
    name = `Personal Teams (${session!.user.email})`;
  } else {
    // type === 'ntfy'
    const customTopic = typeof body.topic === 'string' ? body.topic : null;
    let topic: string;
    if (customTopic !== null) {
      if (!isValidNtfyTopic(customTopic)) {
        return NextResponse.json(
          {
            error: 'Invalid topic',
            message: 'topic must match ^[A-Za-z0-9_-]{6,64}$',
          },
          { status: 400 },
        );
      }
      topic = customTopic;
    } else {
      topic = mintNtfyTopic();
    }
    config = { server_url: 'https://ntfy.sh', topic };
    name = `Personal ntfy (${session!.user.email})`;
  }

  try {
    // UPSERT scoped to calling user + channel type.
    // The partial unique index notification_channels_owner_user_id_channel_type_uniq
    // (Plan 01) closes the race window — concurrent INSERT raises 23505 → 409.
    const result = await postgresClient.query<NotificationChannel>(
      `WITH existing AS (
        SELECT id FROM notification_channels
        WHERE owner_user_id = $1 AND channel_type = $2
        LIMIT 1
      ),
      updated AS (
        UPDATE notification_channels
           SET name = $3, config = $4, is_active = true, updated_at = NOW()
         WHERE id = (SELECT id FROM existing)
         RETURNING *
      ),
      inserted AS (
        INSERT INTO notification_channels (name, channel_type, config, is_active, owner_user_id)
        SELECT $3, $2, $4, true, $1
        WHERE NOT EXISTS (SELECT 1 FROM existing)
        RETURNING *
      )
      SELECT * FROM updated UNION ALL SELECT * FROM inserted`,
      [session!.user.id, type, name, JSON.stringify(config)],
    );

    const row = result.rows[0];
    // Best-effort test send — never blocks save success (D-06).
    const testResult = await sendChannelTest(row);

    return NextResponse.json({
      channel: {
        id: row.id,
        name: row.name,
        channelType: row.channel_type,
        config: row.config,
        isActive: row.is_active,
        ownerUserId: row.owner_user_id,
        createdAt: row.created_at,
        updatedAt: row.updated_at,
      },
      test: testResult,
    });
  } catch (e: unknown) {
    // Partial unique index race: concurrent PUT already inserted the row.
    if (
      e &&
      typeof e === 'object' &&
      'code' in e &&
      (e as { code: string }).code === '23505'
    ) {
      return NextResponse.json(
        { error: 'Conflict', message: 'Channel already exists for this user; retry the save' },
        { status: 409 },
      );
    }
    console.error('PUT /api/me/channels/[type] failed:', e);
    return NextResponse.json(
      { error: 'Failed to save channel', message: e instanceof Error ? e.message : 'unknown' },
      { status: 500 },
    );
  }
}

export async function DELETE(_request: NextRequest, { params }: RouteParams): Promise<NextResponse> {
  const { session, error } = await requireAuth();
  if (error) return error;

  const { type } = await params;
  if (!isPersonalChannelType(type)) {
    return NextResponse.json(
      { error: 'Unsupported channel type', message: `type must be one of: teams, ntfy` },
      { status: 400 },
    );
  }

  try {
    const result = await postgresClient.query<{ id: number }>(
      'DELETE FROM notification_channels WHERE owner_user_id = $1 AND channel_type = $2 RETURNING id',
      [session!.user.id, type],
    );
    if (result.rowCount === 0) {
      return NextResponse.json(
        { error: 'Not found', message: `No ${type} channel found for this user` },
        { status: 404 },
      );
    }
    return NextResponse.json({ deleted: true, channelType: type });
  } catch (e) {
    console.error('DELETE /api/me/channels/[type] failed:', e);
    return NextResponse.json(
      { error: 'Failed to delete channel', message: e instanceof Error ? e.message : 'unknown' },
      { status: 500 },
    );
  }
}
