import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSessionUser } from '@/lib/auth'
import { createAdminClient } from '@/lib/supabase/admin'
import {
  approveClaim, denyClaim, getCompanyContext, logCompanyEvent,
} from '@/lib/data/companies'
import { COMPANY_ROLES, canManageCompany, normalizeMc } from '@/lib/domain/company'
import { slugify } from '@/lib/domain/slug'
import { emailPattern } from '@/lib/like'
import type { CompanyClaim, CompanyMembership, CompanyRole } from '@/types/db'

/**
 * Company Owner / Admin actions (Tasks 91, 92, 94). Every op re-checks that
 * the caller is an APPROVED owner/admin of the company — a broker agent can
 * read /company but no op here accepts them (article §11).
 *
 * ops:
 *   profile   §20 editable fields (+ agents_see_all_trips, §29). An MC change
 *             is NOT saved — it opens an admin review item (§20).
 *   defaults  default permit workflow / payment party on the linked intake page
 *   slug      §16 rename the intake page; old slug redirects (Task 94)
 *   invite    §28 add an existing HeavyHaul account under this company
 *   decide    §28 approve / deny / request more info on a pending claim
 *   role      §28 change a member's role
 *   revoke    §28 remove access
 */

const roleEnum = z.enum(COMPANY_ROLES as [CompanyRole, ...CompanyRole[]])

const schema = z.discriminatedUnion('op', [
  z.object({
    op: z.literal('profile'),
    display_name: z.string().trim().min(1).max(160),
    legal_name: z.string().trim().min(1).max(200),
    mc_number: z.string().trim().max(20),
    dot_number: z.string().trim().max(20),
    physical_address: z.string().trim().max(400),
    corporate_email: z.string().trim().max(200),
    corporate_phone: z.string().trim().max(40),
    website: z.string().trim().max(200),
    notification_recipients: z.string().trim().max(1000),
    agents_see_all_trips: z.boolean(),
  }),
  z.object({
    op: z.literal('defaults'),
    policy: z.enum(['synchron_required', 'upload_allowed', 'ask']),
    payment: z.enum(['broker', 'carrier', 'ask']),
  }),
  z.object({ op: z.literal('slug'), slug: z.string().trim().min(1).max(60) }),
  z.object({ op: z.literal('invite'), email: z.string().trim().email(), role: roleEnum }),
  z.object({
    op: z.literal('decide'),
    claim_id: z.string().uuid(),
    decision: z.enum(['approve', 'deny', 'request_info']),
    note: z.string().trim().max(1000).optional(),
  }),
  z.object({ op: z.literal('role'), membership_id: z.string().uuid(), role: roleEnum }),
  z.object({ op: z.literal('revoke'), membership_id: z.string().uuid() }),
])

export async function POST(req: NextRequest) {
  const user = await getSessionUser()
  if (!user) return NextResponse.json({ error: 'Sign in first.' }, { status: 401 })
  const parsed = schema.safeParse(await req.json().catch(() => ({})))
  if (!parsed.success) return NextResponse.json({ error: 'Invalid request.' }, { status: 400 })
  const input = parsed.data

  const ctx = await getCompanyContext(user.id)
  if (!ctx.available) {
    return NextResponse.json({ error: 'Company verification needs database migration 0013.' }, { status: 503 })
  }
  const company = ctx.company
  if (!company || ctx.membership?.status !== 'approved' || !canManageCompany(ctx.membership.role)) {
    return NextResponse.json({ error: 'Only a Company Owner or Company Admin can do this.' }, { status: 403 })
  }
  if (company.locked) {
    return NextResponse.json({ error: 'This company profile is locked by HeavyHaul Agent.' }, { status: 423 })
  }

  const admin = createAdminClient()
  const actorLabel = user.name || user.email
  const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? null
  const now = new Date().toISOString()
  const audit = (eventType: string, oldValue?: Record<string, unknown> | null, newValue?: Record<string, unknown> | null) =>
    logCompanyEvent({ companyId: company.id, actorUserId: user.id, actorLabel, eventType, oldValue, newValue, ip })

  switch (input.op) {
    case 'profile': {
      const mcTyped = input.mc_number ? normalizeMc(input.mc_number) : null
      if (input.mc_number && !mcTyped) {
        return NextResponse.json({ error: 'Enter the MC number as digits (5–8).' }, { status: 400 })
      }
      const patch: Record<string, unknown> = {
        display_name: input.display_name,
        dot_number: input.dot_number || null,
        physical_address: input.physical_address || null,
        corporate_email: input.corporate_email || null,
        corporate_phone: input.corporate_phone || null,
        website: input.website || null,
        notification_recipients: input.notification_recipients
          .split(',')
          .map((s) => s.trim())
          .filter(Boolean),
        agents_see_all_trips: input.agents_see_all_trips,
        updated_at: now,
      }
      // §20: the FMCSA legal name is locked; the MC number change is an admin decision.
      if (!company.source_fmcsa_id) patch.legal_name = input.legal_name
      let mcReview = false
      if ((mcTyped ?? null) !== (company.mc_number ?? null)) {
        mcReview = true
        const { data: claim } = await admin
          .from('company_claims')
          .insert({
            user_id: user.id,
            company_id: company.id,
            requested_role: ctx.membership.role,
            claim_status: 'admin_review_required',
            verification_method: 'manual_review',
            mc_number: mcTyped,
            review_reason: 'mc_number_change',
            review_notes: `Requested MC change: ${company.mc_number ?? '—'} → ${mcTyped ?? '—'}`,
          })
          .select('id')
          .single()
        await audit('mc_change_requested', { mc_number: company.mc_number }, { mc_number: mcTyped, claim_id: claim?.id })
      }
      const old: Record<string, unknown> = {}
      const changed: Record<string, unknown> = {}
      for (const [k, v] of Object.entries(patch)) {
        if (k === 'updated_at') continue
        const before = (company as unknown as Record<string, unknown>)[k]
        if (JSON.stringify(before ?? null) !== JSON.stringify(v ?? null)) {
          old[k] = before ?? null
          changed[k] = v
        }
      }
      const { error } = await admin.from('companies').update(patch).eq('id', company.id)
      if (error) return NextResponse.json({ error: 'Could not save the profile.' }, { status: 500 })
      if (Object.keys(changed).length > 0) await audit('profile_updated', old, changed)
      return NextResponse.json({ ok: true, mc_review: mcReview })
    }

    case 'defaults': {
      const page = await linkedBrokerPage(company.id)
      if (!page) return NextResponse.json({ error: 'No intake page is linked to this company.' }, { status: 404 })
      const { error } = await admin
        .from('broker_pages')
        .update({ default_permit_policy: input.policy, default_payment_party: input.payment })
        .eq('id', page.id)
      if (error) return NextResponse.json({ error: 'Could not save the defaults.' }, { status: 500 })
      await audit(
        'intake_defaults_updated',
        { policy: page.default_permit_policy, payment: page.default_payment_party },
        { policy: input.policy, payment: input.payment },
      )
      return NextResponse.json({ ok: true })
    }

    case 'slug': {
      const page = await linkedBrokerPage(company.id)
      if (!page) return NextResponse.json({ error: 'No intake page is linked to this company.' }, { status: 404 })
      const slug = slugify(input.slug)
      if (!slug) return NextResponse.json({ error: 'Use letters, numbers and dashes.' }, { status: 400 })
      if (slug === page.slug) return NextResponse.json({ ok: true, slug })
      const { data: taken } = await admin.from('broker_pages').select('id').eq('slug', slug).neq('id', page.id).maybeSingle()
      const { data: historical } = await admin
        .from('broker_page_slug_history')
        .select('broker_page_id')
        .eq('old_slug', slug)
        .neq('broker_page_id', page.id)
        .maybeSingle()
      if (taken || historical) {
        // §16 "disputed slugs require admin review" — not silent: logged and
        // surfaced in the admin company-review queue.
        await audit('slug_conflict', { slug: page.slug }, { requested: slug, held_by: taken?.id ?? historical?.broker_page_id })
        return NextResponse.json(
          { error: `"/intake/${slug}" is used by another company. The conflict was logged for HeavyHaul Agent review.` },
          { status: 409 },
        )
      }
      await admin.from('broker_page_slug_history').insert({ broker_page_id: page.id, old_slug: page.slug })
      const { error } = await admin.from('broker_pages').update({ slug }).eq('id', page.id)
      if (error) return NextResponse.json({ error: 'Could not rename the intake page.' }, { status: 500 })
      await audit('slug_changed', { slug: page.slug }, { slug })
      return NextResponse.json({ ok: true, slug })
    }

    case 'invite': {
      const { data: profile } = await admin
        .from('profiles')
        .select('id, full_name, email')
        .ilike('email', emailPattern(input.email))
        .maybeSingle()
      if (!profile) {
        return NextResponse.json(
          { error: 'No HeavyHaul Agent account uses this email yet. Accounts are created by the administrator during the pilot.' },
          { status: 404 },
        )
      }
      if (profile.id === user.id) return NextResponse.json({ error: 'That is you.' }, { status: 400 })
      await admin.from('company_memberships').upsert(
        {
          user_id: profile.id,
          company_id: company.id,
          role: input.role,
          status: 'approved',
          approved_by: actorLabel,
          approved_at: now,
          revoked_by: null,
          revoked_at: null,
        },
        { onConflict: 'user_id,company_id' },
      )
      await admin.from('company_claims').insert({
        user_id: profile.id,
        company_id: company.id,
        requested_role: input.role,
        claim_status: 'approved',
        verification_method: 'company_admin_approval',
        approval_sent_to: 'company admin',
        approver_name: actorLabel,
        approver_email: user.email,
        approver_ip: ip,
        decided_at: now,
      })
      await audit('member_invited', null, { user_id: profile.id, email: profile.email, role: input.role })
      return NextResponse.json({ ok: true })
    }

    case 'decide': {
      const { data: claimRow } = await admin
        .from('company_claims')
        .select('*')
        .eq('id', input.claim_id)
        .eq('company_id', company.id)
        .maybeSingle()
      const claim = (claimRow ?? null) as CompanyClaim | null
      if (!claim) return NextResponse.json({ error: 'Request not found.' }, { status: 404 })
      if (['approved', 'rejected', 'revoked'].includes(claim.claim_status)) {
        return NextResponse.json({ error: 'This request has already been decided.' }, { status: 409 })
      }
      if (input.decision === 'approve') {
        const result = await approveClaim({
          claim,
          approverLabel: actorLabel,
          approverUserId: user.id,
          approverEmail: user.email,
          ip,
          via: 'company_admin',
        })
        if (!result.ok) return NextResponse.json({ error: result.error }, { status: 400 })
        return NextResponse.json({ ok: true, role: result.role })
      }
      if (input.decision === 'deny') {
        await denyClaim({ claim, approverLabel: actorLabel, approverUserId: user.id, approverEmail: user.email, ip })
        return NextResponse.json({ ok: true })
      }
      // request_info — the user sees the note on their Settings card.
      await admin
        .from('company_claims')
        .update({ claim_status: 'company_verification_pending', info_request: input.note ?? '', updated_at: now })
        .eq('id', claim.id)
      await audit('info_requested', null, { claim_id: claim.id, note: input.note ?? '' })
      return NextResponse.json({ ok: true })
    }

    case 'role':
    case 'revoke': {
      const { data: row } = await admin
        .from('company_memberships')
        .select('*')
        .eq('id', input.membership_id)
        .eq('company_id', company.id)
        .maybeSingle()
      const member = (row ?? null) as CompanyMembership | null
      if (!member) return NextResponse.json({ error: 'Member not found.' }, { status: 404 })
      if (member.user_id === user.id) {
        return NextResponse.json({ error: 'You cannot change your own access. Ask another admin.' }, { status: 400 })
      }
      if (member.role === 'company_owner' && ctx.membership.role !== 'company_owner') {
        return NextResponse.json({ error: 'Only the Company Owner can change the owner.' }, { status: 403 })
      }
      if (input.op === 'role') {
        await admin.from('company_memberships').update({ role: input.role }).eq('id', member.id)
        await audit('role_changed', { user_id: member.user_id, role: member.role }, { user_id: member.user_id, role: input.role })
        return NextResponse.json({ ok: true })
      }
      await admin
        .from('company_memberships')
        .update({ status: 'revoked', revoked_by: actorLabel, revoked_at: now })
        .eq('id', member.id)
      await admin
        .from('company_claims')
        .update({ claim_status: 'revoked', updated_at: now })
        .eq('user_id', member.user_id)
        .eq('company_id', company.id)
        .eq('claim_status', 'approved')
      await audit('access_revoked', { user_id: member.user_id, role: member.role }, null)
      return NextResponse.json({ ok: true })
    }
  }
}

async function linkedBrokerPage(companyId: string) {
  const admin = createAdminClient()
  const { data } = await admin.from('broker_pages').select('*').eq('company_id', companyId).maybeSingle()
  return data as
    | { id: string; slug: string; default_permit_policy: string; default_payment_party: string }
    | null
}
