/*
|--------------------------------------------------------------------------
| Agência PSI — Edge Function: twilio-whatsapp-provision
|--------------------------------------------------------------------------
| Gerencia o ciclo de vida das subcontas Twilio WhatsApp por tenant.
|
| Ações disponíveis (campo `action` no body):
|   provision     — Cria subconta + provisiona número + configura webhook
|   deprovision   — Libera número + fecha subconta
|   suspend       — Suspende subconta (bloqueia envio mas mantém dados)
|   reactivate    — Reativa subconta suspensa
|   sync_usage    — Sincroniza consumo da subconta com twilio_subaccount_usage
|   search_numbers— Lista números disponíveis para compra
|   test_send     — Envia mensagem de teste via subconta
|
| Env vars necessárias (Supabase secrets):
|   TWILIO_ACCOUNT_SID       — Master account SID
|   TWILIO_AUTH_TOKEN        — Master auth token
|   TWILIO_WHATSAPP_WEBHOOK  — URL base do webhook (ex: https://xyz.supabase.co/functions/v1/twilio-whatsapp-webhook)
|   SUPABASE_URL
|   SUPABASE_SERVICE_ROLE_KEY
|   USD_BRL_RATE             — Taxa de câmbio para calcular preço em BRL (ex: 5.20)
|   MARGIN_MULTIPLIER        — Multiplicador de margem (ex: 1.4 = +40% sobre custo)
|--------------------------------------------------------------------------
*/

import { createClient, type SupabaseClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { adminClient, tenantDbForId, listTenantSchemas, type TenantRef } from '../_shared/tenant.ts'

const corsHeaders = {
  'Access-Control-Allow-Origin':  '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
}

// ── Twilio API helper ──────────────────────────────────────────────────────

class TwilioClient {
  private masterSid:   string
  private masterToken: string
  private baseUrl:     string

  constructor(sid: string, token: string) {
    this.masterSid   = sid
    this.masterToken = token
    this.baseUrl     = `https://api.twilio.com/2010-04-01`
  }

  private authHeader(sid?: string, token?: string): string {
    return 'Basic ' + btoa(`${sid ?? this.masterSid}:${token ?? this.masterToken}`)
  }

  async request(
    method: string,
    path: string,
    params?: Record<string, string>,
    subSid?: string,
    subToken?: string
  ): Promise<Record<string, unknown>> {
    const url = `${this.baseUrl}${path}`
    const auth = this.authHeader(subSid, subToken)

    const opts: RequestInit = {
      method,
      headers: {
        'Authorization': auth,
        'Content-Type':  'application/x-www-form-urlencoded',
      },
    }

    if (params && method !== 'GET') {
      opts.body = new URLSearchParams(params).toString()
    }

    const finalUrl = (method === 'GET' && params)
      ? url + '?' + new URLSearchParams(params).toString()
      : url

    const res = await fetch(finalUrl, opts)
    const data = await res.json()

    if (!res.ok) {
      const msg = (data as { message?: string }).message || `Twilio HTTP ${res.status}`
      const code = (data as { code?: number }).code ?? res.status
      throw new TwilioError(msg, code, res.status)
    }

    return data as Record<string, unknown>
  }

  // Cria subconta
  async createSubaccount(friendlyName: string) {
    return this.request('POST', '/Accounts.json', { FriendlyName: friendlyName })
  }

  // Atualiza status da subconta (suspended | active | closed)
  async updateSubaccountStatus(subSid: string, status: string) {
    return this.request('POST', `/Accounts/${subSid}.json`, { Status: status })
  }

  // Busca números disponíveis em um país
  async searchNumbers(country: string, areaCode?: string, smsEnabled = true) {
    const params: Record<string, string> = {
      SmsEnabled: String(smsEnabled),
      Limit: '20',
    }
    if (areaCode) params.AreaCode = areaCode
    return this.request('GET', `/Accounts/${this.masterSid}/AvailablePhoneNumbers/${country}/Local.json`, params)
  }

  // Compra número para a subconta
  async buyNumber(subSid: string, phoneNumber: string, webhookUrl: string, subToken: string) {
    return this.request('POST', `/Accounts/${subSid}/IncomingPhoneNumbers.json`, {
      PhoneNumber:   phoneNumber,
      SmsUrl:        webhookUrl,
      SmsMethod:     'POST',
      StatusCallback: webhookUrl,
    }, subSid, subToken)
  }

  // Libera número da subconta
  async releaseNumber(subSid: string, phoneSid: string, subToken: string) {
    return this.request('DELETE', `/Accounts/${subSid}/IncomingPhoneNumbers/${phoneSid}.json`, undefined, subSid, subToken)
  }

  // Envia mensagem WhatsApp via subconta
  async sendWhatsApp(subSid: string, subToken: string, from: string, to: string, body: string) {
    return this.request('POST', `/Accounts/${subSid}/Messages.json`, {
      From: `whatsapp:${from}`,
      To:   `whatsapp:${to}`,
      Body: body,
    }, subSid, subToken)
  }

  // Busca registros de uso da subconta
  async getUsageRecords(subSid: string, subToken: string, startDate: string, endDate: string) {
    return this.request('GET', `/Accounts/${subSid}/Usage/Records.json`, {
      Category:  'sms-outbound',
      StartDate: startDate,
      EndDate:   endDate,
    }, subSid, subToken)
  }
}

class TwilioError extends Error {
  code:       number
  httpStatus: number
  constructor(message: string, code: number, httpStatus: number) {
    super(message)
    this.name       = 'TwilioError'
    this.code       = code
    this.httpStatus = httpStatus
  }
}

// ── Helpers ───────────────────────────────────────────────────────────────

function ok(data: unknown, status = 200) {
  return new Response(JSON.stringify(data), {
    status,
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
}

function err(message: string, status = 400, detail?: unknown) {
  return new Response(JSON.stringify({ error: message, detail }), {
    status,
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
}

// ── Main handler ──────────────────────────────────────────────────────────

Deno.serve(async (req: Request) => {
  if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })
  if (req.method !== 'POST')    return err('Método não permitido', 405)

  // ── Auth: verifica JWT do usuário ──────────────────────────────
  const authHeader = req.headers.get('authorization') ?? ''
  const supabase   = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
    { global: { headers: { Authorization: authHeader } } }
  )
  const supabaseAdmin = adminClient()

  // ── schema-per-tenant: resolve um canal por id varrendo os schemas tenant ──
  // As ações deprovision/suspend/reactivate/test_send recebem só channel_id e
  // notification_channels vive no schema do tenant (sem coluna tenant_id). Sem o
  // tenant_id no payload, varremos os schemas provisionados pra localizar o canal.
  // Retorna { tdb, channel, ref } ou null se não achar em nenhum schema.
  async function findChannelById(channelId: string): Promise<
    { tdb: SupabaseClient; channel: Record<string, unknown>; ref: TenantRef } | null
  > {
    const refs = await listTenantSchemas(supabaseAdmin)
    for (const ref of refs) {
      const tdb = supabaseAdmin.schema(ref.schema)
      const { data, error } = await tdb
        .from('notification_channels')
        .select('*')
        .eq('id', channelId)
        .maybeSingle()
      if (error) {
        console.warn(`[provision] busca canal em ${ref.schema}:`, error.message)
        continue
      }
      if (data) return { tdb, channel: data as Record<string, unknown>, ref }
    }
    return null
  }

  // Verifica autenticação
  const { data: { user }, error: authErr } = await supabase.auth.getUser()
  if (authErr || !user) return err('Não autorizado', 401)

  // ── Parse body ──────────────────────────────────────────────────
  let body: Record<string, unknown>
  try {
    body = await req.json()
  } catch {
    return err('JSON inválido', 400)
  }

  const action   = body.action as string
  const tenantId = body.tenant_id as string | undefined

  if (!action) return err('Campo `action` obrigatório', 400)

  // ── Config: prioriza tabela saas_twilio_config; cai pra env como fallback ──
  // AUTH_TOKEN é sempre env (nunca DB) por segurança.
  let cfgFromDb: Record<string, unknown> = {}
  try {
    const { data: cfg } = await supabaseAdmin.rpc('get_twilio_config')
    if (cfg && typeof cfg === 'object') cfgFromDb = cfg as Record<string, unknown>
  } catch (_e) { /* tabela ainda não existe ou primeiro deploy → fallback puro env */ }

  const masterSid     = (cfgFromDb.account_sid as string)         || Deno.env.get('TWILIO_ACCOUNT_SID')     || ''
  const masterToken   = Deno.env.get('TWILIO_AUTH_TOKEN')         || ''
  const webhookBase   = (cfgFromDb.whatsapp_webhook_url as string) || Deno.env.get('TWILIO_WHATSAPP_WEBHOOK') || ''
  const usdBrlRate    = Number(cfgFromDb.usd_brl_rate)      || parseFloat(Deno.env.get('USD_BRL_RATE')      ?? '5.5')
  const marginMult    = Number(cfgFromDb.margin_multiplier) || parseFloat(Deno.env.get('MARGIN_MULTIPLIER') ?? '1.4')

  if (!masterSid)   return err('TWILIO_ACCOUNT_SID não configurado. Configure em /saas/twilio-config no painel.', 500)
  if (!masterToken) return err('TWILIO_AUTH_TOKEN não configurado. Rode: supabase secrets set TWILIO_AUTH_TOKEN=...', 500)

  const twilio = new TwilioClient(masterSid, masterToken)

  // ── Verifica se é SaaS admin (para ações administrativas) ───────
  async function isSaasAdmin(): Promise<boolean> {
    const { data } = await supabaseAdmin
      .from('saas_admins')
      .select('id')
      .eq('user_id', user.id)
      .maybeSingle()
    return !!data
  }

  // ── Busca canal do tenant (tabela tenant → schema do tenant) ─────
  async function getChannel(tid: string) {
    const tdb = await tenantDbForId(supabaseAdmin, tid)
    const { data, error } = await tdb
      .from('notification_channels')
      .select('*')
      .eq('channel',   'whatsapp')
      .eq('provider',  'twilio')
      .is('deleted_at', null)
      .maybeSingle()
    if (error) throw error
    return data
  }

  // ── Busca owner_id do tenant ─────────────────────────────────────
  async function getOwnerId(tid: string): Promise<string> {
    const { data, error } = await supabaseAdmin
      .from('tenant_members')
      .select('user_id')
      .eq('tenant_id', tid)
      .in('role', ['tenant_admin', 'admin'])
      .limit(1)
      .single()
    if (error) throw new Error(`Tenant não encontrado: ${error.message}`)
    return data.user_id
  }

  // ══════════════════════════════════════════════════════════════════
  // AÇÃO: provision
  // ══════════════════════════════════════════════════════════════════
  if (action === 'provision') {
    if (!tenantId)            return err('tenant_id obrigatório', 400)
    if (!(await isSaasAdmin())) {
      // Tenants também podem provisionar a si mesmos
      const { data: member } = await supabaseAdmin
        .from('tenant_members')
        .select('tenant_id')
        .eq('tenant_id', tenantId)
        .eq('user_id', user.id)
        .in('role', ['tenant_admin', 'admin'])
        .maybeSingle()
      if (!member) return err('Sem permissão', 403)
    }

    const phoneNumber    = body.phone_number    as string | undefined
    const country        = (body.country        as string) ?? 'BR'
    const displayName    = body.display_name    as string | undefined
    const costPerMsgUsd  = parseFloat((body.cost_per_message_usd  as string) ?? '0.005')
    const pricePerMsgBrl = parseFloat((body.price_per_message_brl as string) ?? String(costPerMsgUsd * usdBrlRate * marginMult))

    try {
      // Verifica se já existe canal twilio para este tenant
      const existing = await getChannel(tenantId)
      if (existing?.twilio_subaccount_sid) {
        return err('Tenant já possui subconta Twilio provisionada', 409)
      }

      const ownerId = await getOwnerId(tenantId)
      const tdb = await tenantDbForId(supabaseAdmin, tenantId)

      // 1. Cria subconta Twilio
      const subaccountData = await twilio.createSubaccount(
        `AgenciaPsi-${tenantId.slice(0, 8)}`
      )
      const subSid   = subaccountData.sid      as string
      const subToken = subaccountData.auth_token as string

      let phoneSid    = ''
      let finalNumber = phoneNumber ?? ''

      // 2. Compra número (se informado ou busca disponível)
      if (!finalNumber) {
        // Busca primeiro número disponível no país
        const available = await twilio.searchNumbers(country)
        const numbers = (available.available_phone_numbers as Array<{ phone_number: string }>) ?? []
        if (!numbers.length) throw new Error(`Nenhum número disponível em ${country}`)
        finalNumber = numbers[0].phone_number
      }

      const webhookUrl = `${webhookBase}?tenant_id=${tenantId}`

      // 3. Compra número na subconta
      const numData = await twilio.buyNumber(subSid, finalNumber, webhookUrl, subToken)
      phoneSid = numData.sid as string

      // 4. Salva no banco (notification_channels é tabela tenant — sem tenant_id)
      const channelData = {
        owner_id:               ownerId,
        channel:                'whatsapp',
        provider:               'twilio',
        is_active:              true,
        display_name:           displayName ?? `WhatsApp Twilio — ${tenantId.slice(0, 8)}`,
        sender_address:         finalNumber,
        connection_status:      'connected',
        credentials:            { subaccount_auth_token: subToken },
        twilio_subaccount_sid:  subSid,
        twilio_phone_number:    finalNumber,
        twilio_phone_sid:       phoneSid,
        webhook_url:            webhookUrl,
        cost_per_message_usd:   costPerMsgUsd,
        price_per_message_brl:  pricePerMsgBrl,
        provisioned_at:         new Date().toISOString(),
      }

      let savedChannel
      if (existing?.id) {
        const { data, error } = await tdb
          .from('notification_channels')
          .update(channelData)
          .eq('id', existing.id)
          .select('*')
          .single()
        if (error) throw error
        savedChannel = data
      } else {
        const { data, error } = await tdb
          .from('notification_channels')
          .insert(channelData)
          .select('*')
          .single()
        if (error) throw error
        savedChannel = data
      }

      return ok({
        success:   true,
        channel:   savedChannel,
        message:   `Subconta Twilio provisionada com número ${finalNumber}`,
      })
    } catch (e) {
      console.error('[provision] erro:', e)
      return err(e instanceof TwilioError ? `Twilio: ${e.message}` : e.message, 500)
    }
  }

  // ══════════════════════════════════════════════════════════════════
  // AÇÃO: deprovision
  // ══════════════════════════════════════════════════════════════════
  if (action === 'deprovision') {
    if (!(await isSaasAdmin())) return err('Apenas SaaS admins podem deprovisionar', 403)

    const channelId = body.channel_id as string
    if (!channelId) return err('channel_id obrigatório', 400)

    try {
      const found = await findChannelById(channelId)
      if (!found) return err('Canal não encontrado', 404)
      const { tdb, channel: ch } = found
      if (!ch.twilio_subaccount_sid) return err('Canal sem subconta Twilio', 400)

      const subToken = (ch.credentials as { subaccount_auth_token?: string } | null)?.subaccount_auth_token as string

      // 1. Libera número
      if (ch.twilio_phone_sid && subToken) {
        try {
          await twilio.releaseNumber(ch.twilio_subaccount_sid as string, ch.twilio_phone_sid as string, subToken)
        } catch (e) {
          console.warn('[deprovision] Erro ao liberar número (ignorado):', (e as Error).message)
        }
      }

      // 2. Fecha subconta
      await twilio.updateSubaccountStatus(ch.twilio_subaccount_sid as string, 'closed')

      // 3. Soft-delete do canal (tabela tenant)
      const { error: delErr } = await tdb
        .from('notification_channels')
        .update({
          deleted_at:        new Date().toISOString(),
          is_active:         false,
          connection_status: 'disconnected',
        })
        .eq('id', channelId)
      if (delErr) throw delErr

      return ok({ success: true, message: 'Subconta encerrada e canal removido' })
    } catch (e) {
      console.error('[deprovision] erro:', e)
      return err(e instanceof TwilioError ? `Twilio: ${e.message}` : e.message, 500)
    }
  }

  // ══════════════════════════════════════════════════════════════════
  // AÇÃO: suspend / reactivate
  // ══════════════════════════════════════════════════════════════════
  if (action === 'suspend' || action === 'reactivate') {
    if (!(await isSaasAdmin())) return err('Apenas SaaS admins podem suspender/reativar', 403)

    const channelId = body.channel_id as string
    if (!channelId) return err('channel_id obrigatório', 400)

    try {
      const found = await findChannelById(channelId)
      if (!found) return err('Canal não encontrado', 404)
      const { tdb, channel: ch } = found
      if (!ch.twilio_subaccount_sid) return err('Canal sem subconta Twilio', 400)

      const twilioStatus = action === 'suspend' ? 'suspended' : 'active'
      await twilio.updateSubaccountStatus(ch.twilio_subaccount_sid as string, twilioStatus)

      await tdb
        .from('notification_channels')
        .update({
          is_active:         action === 'reactivate',
          connection_status: action === 'reactivate' ? 'connected' : 'disconnected',
        })
        .eq('id', channelId)

      return ok({
        success: true,
        message: action === 'suspend' ? 'Subconta suspensa' : 'Subconta reativada',
      })
    } catch (e) {
      console.error(`[${action}] erro:`, e)
      return err(e instanceof TwilioError ? `Twilio: ${e.message}` : e.message, 500)
    }
  }

  // ══════════════════════════════════════════════════════════════════
  // AÇÃO: sync_usage
  // ══════════════════════════════════════════════════════════════════
  if (action === 'sync_usage') {
    if (!(await isSaasAdmin())) return err('Apenas SaaS admins podem sincronizar uso', 403)

    const channelId = body.channel_id as string
    const now       = new Date()
    const startDate = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().split('T')[0]
    const endDate   = now.toISOString().split('T')[0]

    try {
      // schema-per-tenant: notification_channels/logs e twilio_subaccount_usage
      // vivem no schema de cada tenant. Varremos todos os schemas provisionados
      // (ou paramos no que contém channelId, se especificado).
      const refs = await listTenantSchemas(supabaseAdmin)

      const synced = []

      for (const ref of refs) {
        const tdb = supabaseAdmin.schema(ref.schema)

        // Canais twilio do schema (ou somente o especificado)
        let chQuery = tdb
          .from('notification_channels')
          .select('id, twilio_subaccount_sid, credentials, cost_per_message_usd, price_per_message_brl')
          .eq('channel',  'whatsapp')
          .eq('provider', 'twilio')
          .is('deleted_at', null)
          .not('twilio_subaccount_sid', 'is', null)

        if (channelId) chQuery = chQuery.eq('id', channelId)

        const { data: channels, error } = await chQuery
        if (error) {
          console.warn(`[sync_usage] canais em ${ref.schema}:`, error.message)
          continue
        }

        for (const ch of channels ?? []) {
          try {
            const subToken = (ch.credentials as { subaccount_auth_token?: string } | null)?.subaccount_auth_token as string
            if (!subToken) continue

            // Busca mensagens enviadas no mês via notification_logs (tabela tenant)
            const { data: logs } = await tdb
              .from('notification_logs')
              .select('status, estimated_cost_brl')
              .eq('channel', 'whatsapp')
              .eq('provider', 'twilio')
              .gte('created_at', startDate)
              .lte('created_at', endDate + 'T23:59:59Z')

            const sent      = logs?.length ?? 0
            const delivered = logs?.filter(l => l.status === 'delivered' || l.status === 'read').length ?? 0
            const failed    = logs?.filter(l => l.status === 'failed').length ?? 0
            const costBrl   = logs?.reduce((sum, l) => sum + parseFloat(String(l.estimated_cost_brl ?? 0)), 0) ?? 0
            const costUsd   = costBrl / usdBrlRate
            const revBrl    = sent * (ch.price_per_message_brl ?? 0)

            // Upsert no twilio_subaccount_usage (tabela tenant)
            const { error: upsertErr } = await tdb
              .from('twilio_subaccount_usage')
              .upsert({
                channel_id:            ch.id,
                twilio_subaccount_sid: ch.twilio_subaccount_sid,
                period_start:          startDate,
                period_end:            endDate,
                messages_sent:         sent,
                messages_delivered:    delivered,
                messages_failed:       failed,
                cost_usd:              costUsd,
                cost_brl:              costBrl,
                revenue_brl:           revBrl,
                usd_brl_rate:          usdBrlRate,
                synced_at:             new Date().toISOString(),
              }, {
                onConflict: 'channel_id,period_start,period_end',
              })

            if (upsertErr) throw upsertErr
            synced.push({ channel_id: ch.id, sent, delivered, failed })
          } catch (e) {
            console.warn(`[sync_usage] canal ${ch.id}:`, (e as Error).message)
          }
        }

        // Se filtramos por channelId e já achamos, não precisa varrer o resto
        if (channelId && synced.length > 0) break
      }

      return ok({ success: true, synced })
    } catch (e) {
      console.error('[sync_usage] erro:', e)
      return err(e.message, 500)
    }
  }

  // ══════════════════════════════════════════════════════════════════
  // AÇÃO: search_numbers
  // ══════════════════════════════════════════════════════════════════
  if (action === 'search_numbers') {
    if (!(await isSaasAdmin())) return err('Apenas SaaS admins podem buscar números', 403)

    const country  = (body.country   as string) ?? 'US'
    const areaCode = body.area_code  as string | undefined

    try {
      const result  = await twilio.searchNumbers(country, areaCode)
      const numbers = (result.available_phone_numbers as Array<{
        phone_number: string
        friendly_name: string
        locality: string
        region: string
        capabilities: Record<string, boolean>
      }>) ?? []

      return ok({
        numbers: numbers.map(n => ({
          phone_number:  n.phone_number,
          friendly_name: n.friendly_name,
          locality:      n.locality,
          region:        n.region,
          sms:           n.capabilities?.sms ?? false,
          mms:           n.capabilities?.mms ?? false,
          voice:         n.capabilities?.voice ?? false,
        }))
      })
    } catch (e) {
      return err(e instanceof TwilioError ? `Twilio: ${e.message}` : e.message, 500)
    }
  }

  // ══════════════════════════════════════════════════════════════════
  // AÇÃO: test_send
  // ══════════════════════════════════════════════════════════════════
  if (action === 'test_send') {
    const channelId = body.channel_id as string
    const toNumber  = body.to         as string
    const message   = (body.message   as string) ?? 'Mensagem de teste — AgenciaPsi'

    if (!channelId || !toNumber) return err('channel_id e to são obrigatórios', 400)

    // Tenant pode testar o seu próprio canal; admin pode testar qualquer um
    try {
      const found = await findChannelById(channelId)
      if (!found) return err('Canal não encontrado', 404)
      const { channel: ch, ref } = found

      // Verifica permissão: próprio tenant (via ref.tenantId) ou admin
      const admin = await isSaasAdmin()
      if (!admin) {
        const { data: member } = await supabaseAdmin
          .from('tenant_members')
          .select('tenant_id')
          .eq('tenant_id', ref.tenantId)
          .eq('user_id', user.id)
          .maybeSingle()
        if (!member) return err('Sem permissão', 403)
      }

      const subToken = (ch.credentials as { subaccount_auth_token?: string } | null)?.subaccount_auth_token as string
      if (!ch.twilio_subaccount_sid || !subToken) return err('Canal não provisionado', 400)

      const result = await twilio.sendWhatsApp(
        ch.twilio_subaccount_sid as string,
        subToken,
        ch.twilio_phone_number as string,
        toNumber,
        message
      )

      return ok({
        success:    true,
        message_sid: result.sid,
        status:     result.status,
      })
    } catch (e) {
      return err(e instanceof TwilioError ? `Twilio: ${e.message}` : e.message, 500)
    }
  }

  return err(`Ação desconhecida: ${action}`, 400)
})
