import { Hono, type Context } from 'hono'
import { z } from 'zod'
import { TenantTier } from '@zync/types'
import { requireTier } from '@zync/auth'
import type { AppEnv } from '../../types'
import { isOwnerOrAdminRole } from '../../lib/system-roles'
import { authMiddleware } from '../../middleware/auth'
import { requirePermission } from '../../middleware/guards'
import {
  createDb,
  findUserById,
  getSmtpSettings,
  updateSmtpSettings,
} from '@zync/db/queries'
import { encryptCredential } from '@zync/auth'
import { validateSafeOutboundUrl } from '@zync/utils'
import { testSmtpConnection } from '../../services/onboarding-email-test'

const FREE_EMAIL_DOMAINS = new Set([
  '013.net',
  'bezeqint.net',
  'gmail.com',
  'hotmail.com',
  'outlook.com',
  'walla.co.il',
  'yahoo.com',
])

const smtpEncryptionSchema = z.enum(['tls', 'starttls', 'none'])

const patchSmtpSchema = z.object({
  host: z.string().min(1).max(253).optional(),
  port: z.number().int().min(1).max(65535).optional(),
  username: z.string().max(200).optional(),
  password: z.string().max(500).optional(),
  fromName: z.string().max(50).optional(),
  fromEmail: z.string().email().optional(),
  replyTo: z.string().email().nullable().optional(),
  tls: z.boolean().optional(),
  smtpEnabled: z.boolean().optional(),
  smtpFallbackEnabled: z.boolean().optional(),
  smtpEncryption: smtpEncryptionSchema.optional(),
  smtpFromOverride: z.string().email().nullable().optional(),
  customDomain: z.string().max(253).nullable().optional(),
  brandColor: z.string().max(50).nullable().optional(),
  logoUrl: z.string().url().nullable().optional(),
  from_name: z.string().max(50).optional(),
  from_email: z.string().email().optional(),
  smtp_host: z.string().min(1).max(253).optional(),
  smtp_port: z.number().int().min(1).max(65535).optional(),
  smtp_username: z.string().max(200).optional(),
  smtp_password: z.string().max(500).optional(),
  smtp_enabled: z.boolean().optional(),
  smtp_fallback_enabled: z.boolean().optional(),
  smtp_encryption: smtpEncryptionSchema.optional(),
  smtp_from_override: z.string().email().nullable().optional(),
})

const testEmailSchema = z.object({
  to: z.string().email().optional(),
})

type ResendDomainRecord = { record: string; name?: string; value?: string }

type RouteContext = Context<AppEnv>
type UserSession = {
  type: 'user'
  sub: string
  tid: string
  role: string
  tier: TenantTier
  permissions: string[]
}

function getActorIp(c: RouteContext) {
  return c.req.header('CF-Connecting-IP') ?? null
}

function getRequestId(c: RouteContext) {
  return c.req.header('CF-Ray') ?? null
}

function normalizePatch(input: z.infer<typeof patchSmtpSchema>) {
  const smtpEncryption = input.smtp_encryption ?? input.smtpEncryption
  const tls = input.tls ?? (smtpEncryption ? smtpEncryption !== 'none' : undefined)
  return {
    host: input.smtp_host ?? input.host,
    port: input.smtp_port ?? input.port,
    username: input.smtp_username ?? input.username,
    password: input.smtp_password ?? input.password,
    fromName: input.from_name ?? input.fromName,
    fromEmail: input.from_email ?? input.fromEmail,
    replyTo: input.replyTo,
    tls,
    smtpEncryption: smtpEncryption ?? (tls === false ? 'none' : undefined),
    smtpEnabled: input.smtp_enabled ?? input.smtpEnabled,
    smtpFallbackEnabled: input.smtp_fallback_enabled ?? input.smtpFallbackEnabled,
    smtpFromOverride: input.smtp_from_override ?? input.smtpFromOverride,
    customDomain: input.customDomain,
    brandColor: input.brandColor,
    logoUrl: input.logoUrl,
  }
}

function isFreeEmailDomain(email: string) {
  const domain = email.split('@')[1]?.toLowerCase()
  return !!domain && FREE_EMAIL_DOMAINS.has(domain)
}

function mapSettingsResponse(settings: Awaited<ReturnType<typeof getSmtpSettings>>) {
  return {
    from_name: settings.fromName,
    from_email: settings.fromEmail,
    domain_verified: settings.domainVerified,
    domain_verified_at: settings.domainVerifiedAt,
    smtp_host: settings.host,
    smtp_port: settings.port,
    smtp_username: settings.username,
    smtp_encryption: settings.smtpEncryption,
    smtp_from_override: settings.smtpFromOverride,
    smtp_enabled: settings.smtpEnabled,
    smtp_fallback_enabled: settings.smtpFallbackEnabled,
    smtp_password_set: settings.encryptedPassword !== null,
    host: settings.host,
    port: settings.port,
    username: settings.username,
    hasPassword: settings.encryptedPassword !== null,
    fromName: settings.fromName,
    fromEmail: settings.fromEmail,
    replyTo: settings.replyTo,
    tls: settings.tls,
    smtpEnabled: settings.smtpEnabled,
    smtpFallbackEnabled: settings.smtpFallbackEnabled,
    smtpEncryption: settings.smtpEncryption,
    smtpFromOverride: settings.smtpFromOverride,
    customDomain: settings.customDomain,
    brandColor: settings.brandColor,
    logoUrl: settings.logoUrl,
  }
}

async function ensureBusinessAccess(c: RouteContext): Promise<Response | null> {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerOrAdminRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }
  return null
}

function getUserSession(c: RouteContext): UserSession {
  return c.get('session') as UserSession
}

async function createResendDomain(apiKey: string, domain: string) {
  const res = await fetch('https://api.resend.com/domains', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ name: domain }),
  })
  if (!res.ok) {
    const body = await res.text()
    throw new Error(body || `Resend API error ${res.status}`)
  }
  const body = (await res.json()) as {
    id: string
    records?: ResendDomainRecord[]
  }
  return body
}

async function verifyResendDomain(apiKey: string, domainId: string) {
  const verifyRes = await fetch(`https://api.resend.com/domains/${domainId}/verify`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}` },
  })
  if (!verifyRes.ok) {
    const body = await verifyRes.text()
    throw new Error(body || `Resend API error ${verifyRes.status}`)
  }

  const statusRes = await fetch(`https://api.resend.com/domains/${domainId}`, {
    headers: { Authorization: `Bearer ${apiKey}` },
  })
  if (!statusRes.ok) {
    const body = await statusRes.text()
    throw new Error(body || `Resend API error ${statusRes.status}`)
  }

  return (await statusRes.json()) as {
    id: string
    name: string
    status?: string
    records?: ResendDomainRecord[]
  }
}

function mapVerificationRecords(domain: string, records: ResendDomainRecord[] | undefined) {
  const dkim = records?.find((record) => record.record === 'DKIM')
  return [
    {
      type: 'TXT',
      host: dkim?.name ?? `resend._domainkey.${domain}`,
      value: dkim?.value ?? '',
      status: 'pending' as const,
    },
    {
      type: 'TXT',
      host: domain,
      value: 'v=spf1 include:spf.resend.com ~all',
      status: 'pending' as const,
    },
  ]
}

export const smtpSettingsRoute = new Hono<AppEnv>()
smtpSettingsRoute.use('*', authMiddleware)
smtpSettingsRoute.use('*', requireTier(TenantTier.BUSINESS))

smtpSettingsRoute.get('/', async (c) => {
  const denied = await ensureBusinessAccess(c)
  if (denied) return denied

  const db = c.get('db') ?? createDb(c.env)
  const settings = await getSmtpSettings(db, getUserSession(c).tid)
  return c.json(mapSettingsResponse(settings), 200)
})

smtpSettingsRoute.patch(
  '/',
  requirePermission('settings:write'),
  async (c) => {
    const denied = await ensureBusinessAccess(c)
    if (denied) return denied

    const body = await c.req.json().catch(() => null)
    const parsed = patchSmtpSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
    }

    const patch = normalizePatch(parsed.data)
    const session = getUserSession(c)

    if (patch.fromEmail && isFreeEmailDomain(patch.fromEmail)) {
      return c.json({ error: 'From email must use a custom domain' }, 400)
    }

    if (patch.host !== undefined) {
      const hostCheck = validateSafeOutboundUrl(`https://${patch.host}`)
      if (!hostCheck.ok) {
        return c.json({ error: 'SMTP host must be a public hostname' }, 400)
      }
    }

    if (patch.logoUrl) {
      const logoCheck = validateSafeOutboundUrl(patch.logoUrl)
      if (!logoCheck.ok) {
        return c.json({ error: 'Logo URL must be a public HTTPS endpoint' }, 400)
      }
    }

    const enterpriseOnly =
      patch.smtpEnabled !== undefined ||
      patch.smtpFallbackEnabled !== undefined ||
      patch.smtpEncryption !== undefined ||
      patch.smtpFromOverride !== undefined ||
      patch.host !== undefined ||
      patch.port !== undefined ||
      patch.username !== undefined ||
      patch.password !== undefined

    if (enterpriseOnly && session.tier !== TenantTier.ENTERPRISE && session.tier !== TenantTier.WHITE_LABEL) {
      return c.json({ error: 'Upgrade required', requiredTier: TenantTier.ENTERPRISE }, 402)
    }

    let encryptedPassword: string | undefined
    if (patch.password) {
      const blob = await encryptCredential(patch.password, c.env?.INTEGRATION_ENCRYPTION_KEY)
      encryptedPassword = JSON.stringify(blob)
    }

    const db = c.get('db') ?? createDb(c.env)
    await updateSmtpSettings(
      db,
      session.tid,
      session.sub,
      {
        host: patch.host,
        port: patch.port,
        username: patch.username,
        fromName: patch.fromName,
        fromEmail: patch.fromEmail,
        replyTo: patch.replyTo,
        tls: patch.tls,
        smtpEncryption: patch.smtpEncryption,
        smtpEnabled: patch.smtpEnabled,
        smtpFallbackEnabled: patch.smtpFallbackEnabled,
        smtpFromOverride: patch.smtpFromOverride,
        customDomain: patch.customDomain,
        brandColor: patch.brandColor,
        logoUrl: patch.logoUrl,
        ...(encryptedPassword !== undefined ? { encryptedPassword } : {}),
      },
      {
        actorIp: getActorIp(c),
        requestId: getRequestId(c),
      },
    )

    return c.json({ ok: true }, 200)
  },
)

smtpSettingsRoute.post(
  '/verify-domain',
  requirePermission('settings:write'),
  async (c) => {
    const denied = await ensureBusinessAccess(c)
    if (denied) return denied

    const session = getUserSession(c)
    const db = c.get('db') ?? createDb(c.env)
    const settings = await getSmtpSettings(db, session.tid)
    const fromEmail = settings.fromEmail
    if (!fromEmail) {
      return c.json({ error: 'Configure from_email first' }, 422)
    }

    const domain = fromEmail.split('@')[1]
    if (!domain) {
      return c.json({ error: 'Invalid from_email domain' }, 400)
    }

    try {
      let domainId = settings.domainVerificationToken
      if (!domainId) {
        const created = await createResendDomain(c.env.RESEND_API_KEY, domain)
        domainId = created.id
      }

      const verified = await verifyResendDomain(c.env.RESEND_API_KEY, domainId)
      const isVerified = verified.status === 'verified'

      await updateSmtpSettings(
        db,
        session.tid,
        session.sub,
        {
          domainVerificationToken: domainId,
          domainVerified: isVerified,
          domainVerifiedAt: isVerified ? new Date().toISOString() : null,
        },
        {
          actorIp: getActorIp(c),
          requestId: getRequestId(c),
        },
      )

      return c.json({
        verified: isVerified,
        records: mapVerificationRecords(domain, verified.records).map((record) => ({
          ...record,
          status: isVerified ? 'verified' : 'pending',
        })),
      }, 200)
    } catch (error) {
      return c.json({
        error: error instanceof Error ? error.message : 'Domain verification failed',
      }, 502)
    }
  },
)

smtpSettingsRoute.post(
  '/test',
  requirePermission('settings:write'),
  async (c) => {
    const denied = await ensureBusinessAccess(c)
    if (denied) return denied

    const body = await c.req.json().catch(() => ({}))
    const parsed = testEmailSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request' }, 400)
    }

    const session = getUserSession(c)
    if (session.tier !== TenantTier.ENTERPRISE && session.tier !== TenantTier.WHITE_LABEL) {
      return c.json({ error: 'Upgrade required', requiredTier: TenantTier.ENTERPRISE }, 402)
    }

    const db = c.get('db') ?? createDb(c.env)
    const settings = await getSmtpSettings(db, session.tid)
    if (!settings.host || !settings.username || !settings.encryptedPassword) {
      return c.json({ success: false, error: 'SMTP not fully configured' }, 422)
    }

    const user = parsed.data.to ? null : await findUserById(db, session.sub as Parameters<typeof findUserById>[1])
    const to = parsed.data.to ?? user?.email ?? null
    if (!to) {
      return c.json({ success: false, error: 'Could not resolve recipient email' }, 422)
    }

    const result = await testSmtpConnection({
      host: settings.host,
      port: settings.port ?? 587,
      username: settings.username,
      password: settings.encryptedPassword,
    })

    if (!result.ok) {
      return c.json({ success: false, error: result.error }, 200)
    }

    return c.json({ success: true }, 200)
  },
)
