import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { wrapHandler, logError } from '../_shared/error-handler.ts'

// Helper function to convert ArrayBuffer to hex string
function bufferToHex(buffer: ArrayBuffer): string {
  const byteArray = new Uint8Array(buffer);
  return Array.from(byteArray)
    .map((byte) => byte.toString(16).padStart(2, "0"))
    .join("");
}

/**
 * Makes an HTTP/1.0 request using raw TCP/TLS connection
 * This is necessary because ShopRenter API requires HTTP/1.0
 */
async function makeHttp10Request(
  hostname: string,
  path: string,
  method: string,
  headers: Record<string, string>,
  body?: string
): Promise<{ status: number; statusText: string; headers: Record<string, string>; body: string }> {
  const conn = await Deno.connectTls({
    hostname: hostname,
    port: 443,
  })

  try {
    const requestLines: string[] = [
      `${method} ${path} HTTP/1.0`,
      `Host: ${hostname}`,
    ]

    for (const [key, value] of Object.entries(headers)) {
      requestLines.push(`${key}: ${value}`)
    }

    requestLines.push('Connection: close')
    requestLines.push('')
    requestLines.push('')

    let request = requestLines.join('\r\n')

    if (body) {
      request = request + body
    }

    const encoder = new TextEncoder()
    await conn.write(encoder.encode(request))

    const chunks: Uint8Array[] = []
    const buffer = new Uint8Array(64 * 1024)
    let totalBytes = 0

    while (true) {
      try {
        const n = await conn.read(buffer)
        if (n === null) break

        const chunk = new Uint8Array(n)
        chunk.set(buffer.subarray(0, n))
        chunks.push(chunk)
        totalBytes += n
      } catch (e) {
        if (e instanceof Deno.errors.UnexpectedEof ||
            (e instanceof Error && e.name === 'UnexpectedEof')) {
          break
        }
        throw e
      }
    }

    const responseData = new Uint8Array(totalBytes)
    let offset = 0
    for (const chunk of chunks) {
      responseData.set(chunk, offset)
      offset += chunk.length
    }

    const decoder = new TextDecoder()

    let headerEndIndex = -1
    let headerSeparatorLength = 0

    for (let i = 0; i < responseData.length - 3; i++) {
      if (responseData[i] === 13 && responseData[i + 1] === 10 &&
          responseData[i + 2] === 13 && responseData[i + 3] === 10) {
        headerEndIndex = i
        headerSeparatorLength = 4
        break
      }
    }

    if (headerEndIndex === -1) {
      for (let i = 0; i < responseData.length - 1; i++) {
        if (responseData[i] === 10 && responseData[i + 1] === 10) {
          headerEndIndex = i
          headerSeparatorLength = 2
          break
        }
      }
    }

    if (headerEndIndex === -1) {
      throw new Error('Invalid HTTP response: no header/body separator found')
    }

    const headerBytes = responseData.subarray(0, headerEndIndex)
    const headerSection = decoder.decode(headerBytes)
    const bodyBytes = responseData.subarray(headerEndIndex + headerSeparatorLength)

    const lines = headerSection.split(/\r?\n/)
    const statusLine = lines[0]
    const statusMatch = statusLine.match(/HTTP\/1\.[01]\s+(\d+)\s+(.*)/)

    if (!statusMatch) {
      throw new Error(`Invalid HTTP status line: ${statusLine}`)
    }

    const status = parseInt(statusMatch[1])
    const statusText = statusMatch[2]

    const responseHeaders: Record<string, string> = {}
    for (let i = 1; i < lines.length; i++) {
      const colonIndex = lines[i].indexOf(':')
      if (colonIndex > 0) {
        const key = lines[i].substring(0, colonIndex).trim()
        const value = lines[i].substring(colonIndex + 1).trim()
        responseHeaders[key] = value
      }
    }

    const bodyText = decoder.decode(bodyBytes)

    return {
      status,
      statusText,
      headers: responseHeaders,
      body: bodyText,
    }
  } finally {
    try {
      conn.close()
    } catch (e) {
      // Gracefully handle connection closure errors
    }
  }
}

// Exchange code for access token using client_credentials grant
async function getTokenWithClientCredentials(shopname: string, clientId: string, clientSecret: string) {
  const hostname = 'oauth.app.shoprenter.net'
  const path = `/${shopname}/app/token`

  console.log(`[ShopRenter] Requesting token for ${shopname} using client_credentials`)

  const requestBody = JSON.stringify({
    grant_type: 'client_credentials',
    client_id: clientId,
    client_secret: clientSecret
  })

  const requestHeaders: Record<string, string> = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'Content-Length': requestBody.length.toString()
  }

  try {
    const response = await makeHttp10Request(hostname, path, 'POST', requestHeaders, requestBody)

    if (response.status !== 200) {
      console.error('[ShopRenter] Token request error:', response.body)
      throw new Error(`Failed to get access token: ${response.status} ${response.body}`)
    }

    const data = JSON.parse(response.body)
    console.log(`[ShopRenter] Access token obtained for ${shopname}`)
    return data
  } catch (error) {
    console.error('[ShopRenter] Token request error:', error)
    throw new Error('Failed to get access token via client_credentials')
  }
}

// Calculate HMAC-SHA256 using Deno's native crypto.subtle API
async function calculateHmacSha256(secret: string, message: string): Promise<string> {
  const encoder = new TextEncoder();
  const keyData = encoder.encode(secret);
  const key = await crypto.subtle.importKey(
    "raw",
    keyData,
    { name: "HMAC", hash: { name: "SHA-256" } },
    false,
    ["sign"]
  );
  const messageData = encoder.encode(message);
  const signature = await crypto.subtle.sign("HMAC", key, messageData);
  return bufferToHex(signature);
}

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

// Validate HMAC signature from ShopRenter
// Per ShopRenter documentation, HMAC is calculated from the query string without HMAC parameter
// IMPORTANT: We must preserve the original order of parameters as received in the URL
async function validateHMAC(rawQueryString: string, clientSecret: string, clientId: string): Promise<boolean> {
  if (!clientSecret) {
    console.error('[ShopRenter] Client secret is empty or undefined')
    return false
  }

  // Parse the query string to get the hmac value
  const params = new URLSearchParams(rawQueryString)
  const hmacValue = params.get('hmac')
  if (!hmacValue) {
    console.error('[ShopRenter] HMAC missing from request')
    return false
  }

  // Build the params string by preserving the original order from the URL
  // and excluding the hmac and app_url parameters
  // ShopRenter calculates HMAC using: shopname=...&code=...&timestamp=... (original order)
  const paramsWithoutHmac = rawQueryString
    .split('&')
    .filter(param => {
      const key = param.split('=')[0]
      return key !== 'hmac' && key !== 'app_url'
    })
    .join('&')

  console.log(`[ShopRenter] HMAC validation - params (original order): ${paramsWithoutHmac}`)
  console.log(`[ShopRenter] HMAC validation - client secret length: ${clientSecret.length}`)

  // Calculate HMAC using Deno's native crypto.subtle API (SHA-256)
  const calculatedHmacWithSecret = await calculateHmacSha256(clientSecret, paramsWithoutHmac)

  console.log(`[ShopRenter] HMAC validation - received hmac: ${hmacValue}`)
  console.log(`[ShopRenter] HMAC validation - calculated hmac (with secret): ${calculatedHmacWithSecret}`)

  // Compare HMACs
  const resultWithSecret = calculatedHmacWithSecret === hmacValue
  console.log(`[ShopRenter] HMAC validation result (with secret): ${resultWithSecret}`)

  if (resultWithSecret) {
    return true
  }

  // Fallback: try with Client ID (in case of documentation confusion)
  const calculatedHmacWithId = await calculateHmacSha256(clientId, paramsWithoutHmac)
  console.log(`[ShopRenter] HMAC validation - calculated hmac (with id): ${calculatedHmacWithId}`)

  const resultWithId = calculatedHmacWithId === hmacValue
  console.log(`[ShopRenter] HMAC validation result (with id): ${resultWithId}`)

  return resultWithId
}

// Validate timestamp to prevent replay attacks
// Note: This function now only logs warnings instead of rejecting requests
// because ShopRenter's timestamp generation may have inconsistencies.
// HMAC validation is the primary security check.
function validateTimestamp(timestamp: string, maxAgeSeconds = 300): boolean {
  const requestTime = parseInt(timestamp, 10)
  const currentTime = Math.floor(Date.now() / 1000)
  const age = currentTime - requestTime

  if (age < -60) {
    // Allow up to 60 seconds of clock skew for future timestamps
    // Log warning for debugging but don't reject
    console.warn(`[ShopRenter] Request timestamp is in the future by ${-age}s - allowing due to potential clock skew or ShopRenter timestamp issues`)
  }

  if (age > maxAgeSeconds) {
    console.warn(`[ShopRenter] Request timestamp is old: ${age}s > ${maxAgeSeconds}s - allowing due to potential ShopRenter timestamp issues`)
  }

  // Always return true - we rely on HMAC validation for security
  // Timestamp validation is informational only
  return true
}

// Exchange authorization code for access token
async function exchangeCodeForToken(shopname: string, code: string, clientId: string, clientSecret: string, redirectUri: string) {
  const tokenUrl = `https://${shopname}.shoprenter.hu/oauth/token`

  try {
    const response = await fetch(tokenUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        client_id: clientId,
        client_secret: clientSecret,
        code: code,
        redirect_uri: redirectUri
      })
    })

    if (!response.ok) {
      const errorData = await response.text()
      console.error('[ShopRenter] Token exchange error:', errorData)
      throw new Error('Failed to exchange code for token')
    }

    const data = await response.json()
    console.log(`[ShopRenter] Token acquired for ${shopname}`)
    return data
  } catch (error) {
    console.error('[ShopRenter] Token exchange error:', error)
    throw new Error('Failed to exchange code for token')
  }
}

serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    const url = new URL(req.url)
    const shopname = url.searchParams.get('shopname')
    const code = url.searchParams.get('code')
    const timestamp = url.searchParams.get('timestamp')
    const hmac = url.searchParams.get('hmac')
    const app_url = url.searchParams.get('app_url')

    console.log(`[ShopRenter] OAuth callback received for ${shopname}`)

    // Check required parameters
    if (!shopname || !code || !timestamp || !hmac) {
      return new Response(
        JSON.stringify({ error: 'Missing required parameters' }),
        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // Get environment variables (support both naming conventions with fallback)
    const shoprenterClientId = Deno.env.get('SHOPRENTER_APP_CLIENT_ID') || Deno.env.get('SHOPRENTER_CLIENT_ID')
    const shoprenterClientSecret = Deno.env.get('SHOPRENTER_APP_CLIENT_SECRET') || Deno.env.get('SHOPRENTER_CLIENT_SECRET')
    const frontendUrl = Deno.env.get('FRONTEND_URL') || 'https://shopcall.ai'
    const supabaseUrl = Deno.env.get('SUPABASE_URL')!
    const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!

    // Log which environment variables are being used
    console.log(`[ShopRenter] Using client ID from: ${Deno.env.get('SHOPRENTER_APP_CLIENT_ID') ? 'SHOPRENTER_APP_CLIENT_ID' : 'SHOPRENTER_CLIENT_ID'}`)
    console.log(`[ShopRenter] Using client secret from: ${Deno.env.get('SHOPRENTER_APP_CLIENT_SECRET') ? 'SHOPRENTER_APP_CLIENT_SECRET' : 'SHOPRENTER_CLIENT_SECRET'}`)
    // Log first 4 and last 4 chars of secret for debugging (safe to expose)
    if (shoprenterClientSecret) {
      const secretPreview = shoprenterClientSecret.length > 8
        ? `${shoprenterClientSecret.substring(0, 4)}...${shoprenterClientSecret.substring(shoprenterClientSecret.length - 4)}`
        : '(too short)';
      console.log(`[ShopRenter] Client secret preview: ${secretPreview} (length: ${shoprenterClientSecret.length})`)
    }

    if (!shoprenterClientId || !shoprenterClientSecret) {
      console.error('ShopRenter client credentials not configured. Set either SHOPRENTER_APP_CLIENT_ID/SECRET or SHOPRENTER_CLIENT_ID/SECRET')
      return new Response(
        JSON.stringify({ error: 'ShopRenter integration not configured' }),
        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // Validate timestamp
    if (!validateTimestamp(timestamp)) {
      return new Response(
        JSON.stringify({ error: 'Request timestamp invalid or expired' }),
        { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // Validate HMAC using the raw query string to preserve parameter order
    const rawQueryString = url.search.substring(1) // Remove leading '?'
    if (!(await validateHMAC(rawQueryString, shoprenterClientSecret, shoprenterClientId))) {
      return new Response(
        JSON.stringify({ error: 'HMAC validation failed' }),
        { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // After validating HMAC, exchange code for tokens and store pending installation
    const supabase = createClient(supabaseUrl, supabaseServiceKey)

    // Exchange code for access token using client_credentials grant
    // ShopRenter uses client_credentials flow after app installation
    let tokenData
    try {
      tokenData = await getTokenWithClientCredentials(shopname, shoprenterClientId, shoprenterClientSecret)
      console.log(`[ShopRenter] Token exchange successful for ${shopname}`)
    } catch (tokenError) {
      console.error('[ShopRenter] Token exchange failed:', tokenError)
      const errorRedirectUrl = `${frontendUrl}/integrations?error=token_exchange_failed&shopname=${encodeURIComponent(shopname)}`
      return new Response(null, {
        status: 302,
        headers: {
          ...corsHeaders,
          'Location': errorRedirectUrl
        }
      })
    }

    // Generate a unique installation ID
    const installationId = crypto.randomUUID()

    // Look up phone_number_id from oauth_states if available
    const { data: oauthState } = await supabase
      .from('oauth_states')
      .select('phone_number_id')
      .eq('platform', 'shoprenter')
      .eq('shopname', shopname)
      .order('created_at', { ascending: false })
      .limit(1)
      .single()

    const phoneNumberId = oauthState?.phone_number_id || null

    // Calculate token expiration
    const expiresIn = tokenData.expires_in || 3600 // Default to 1 hour
    const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString()

    // Store pending installation with tokens
    const { error: insertError } = await supabase
      .from('pending_shoprenter_installs')
      .insert({
        installation_id: installationId,
        shopname,
        access_token: tokenData.access_token,
        refresh_token: tokenData.refresh_token || null,
        token_type: tokenData.token_type || 'Bearer',
        expires_in: expiresIn,
        scopes: tokenData.scope ? tokenData.scope.split(' ') : null,
        expires_at: new Date(Date.now() + 15 * 60 * 1000).toISOString(), // 15 minutes to complete installation
        phone_number_id: phoneNumberId
      })

    if (insertError) {
      console.error('[ShopRenter] Error storing pending installation:', insertError)
      return new Response(
        JSON.stringify({ error: 'Failed to process installation' }),
        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // Redirect to app_url (ShopRenter admin) with installation ID
    // This allows ShopRenter to redirect back to our EntryPoint with the sr_install parameter
    // Per ShopRenter docs: "It is possible to decorate the value of app_url with unique query string parameters"
    let redirectUrl: string

    if (app_url) {
      // Append our installation ID to the app_url
      const appUrlObj = new URL(app_url)
      appUrlObj.searchParams.set('sr_install', installationId)
      appUrlObj.searchParams.set('shopname', shopname)
      redirectUrl = appUrlObj.toString()
      console.log(`[ShopRenter] Installation pending for ${shopname}, redirecting to app_url: ${redirectUrl}`)
    } else {
      // Fallback: redirect directly to our integrations page if no app_url provided
      redirectUrl = `${frontendUrl}/integrations?sr_install=${installationId}&shopname=${encodeURIComponent(shopname)}`
      console.log(`[ShopRenter] Installation pending for ${shopname}, no app_url provided, redirecting to: ${redirectUrl}`)
    }

    return new Response(null, {
      status: 302,
      headers: {
        ...corsHeaders,
        'Location': redirectUrl
      }
    })

  } catch (error) {
    console.error('[ShopRenter] OAuth callback error:', error)
    return new Response(
      JSON.stringify({ error: 'Failed to process OAuth callback' }),
      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
    )
  }
}))
