import Link from 'next/link'
import { createClient } from '@/lib/supabase/server'
import { getAppOrg } from '@/lib/org'
import {
  getEffectiveUser,
  getViewAsImportScope,
} from '@/lib/effective-user'
import { canManageImports, canTransfer } from '@/lib/permissions'
import { FilterBar, type FilterOptions } from '@/components/filter-bar'
import { readFiltersFromSearchParams } from '@/lib/filters'
import { ProjectsTable, type ProjectRow, type FilterSnapshot } from './projects-table'
import { QuickFilters } from './quick-filters'

type SummaryRpcRow = {
  operator: string | null
  client: string | null
  project: string | null
  /** projects.id when this group matches an entity; NULL otherwise. */
  project_entity_id: string | null
  row_count: number
  billout_seconds: number
  cost_usd: number | string
  // Owner-only column at the app layer (rendered only for owners).
  billout_amount_usd: number | string | null
  // Adjusted billout: raw × (1 + pct/100) + amount. Owner-only.
  billout_adjusted_usd: number | string | null
  billout_adjustment_pct: number | string | null
  billout_adjustment_amount: number | string | null
  // Only populated for owners (RLS on the `projects` table hides it for managers).
  income_usd: number | string | null
}

type OptionsRow = {
  operators: string[] | null
  clients: string[] | null
  projects: string[] | null
  users: string[] | null
  source_emails: string[] | null
}

const SORT_KEYS = [
  'operator',
  'client',
  'project',
  'row_count',
  'billout_hrs',
  'cost',
  'billout_amount',
  'income',
  'margin',
] as const
type SortKey = (typeof SORT_KEYS)[number]

export default async function ProjectsPage({
  searchParams,
}: {
  searchParams: Promise<Record<string, string | string[] | undefined>>
}) {
  const sp = await searchParams
  const sortKeyRaw = typeof sp.sort === 'string' ? sp.sort : 'billout_hrs'
  const sortKey: SortKey = (SORT_KEYS as readonly string[]).includes(sortKeyRaw)
    ? (sortKeyRaw as SortKey)
    : 'billout_hrs'
  const sortDir = (typeof sp.dir === 'string' ? sp.dir : 'desc') as
    | 'asc'
    | 'desc'

  const expandRaw = typeof sp.expand === 'string' ? sp.expand : ''
  const expandKeys = new Set(
    expandRaw.split(',').map((s) => s.trim()).filter(Boolean),
  )

  const error = typeof sp.error === 'string' ? sp.error : null
  const info = typeof sp.info === 'string' ? sp.info : null
  const bulkEdited =
    typeof sp.bulk_edited === 'string' ? Number(sp.bulk_edited) : null

  const supabase = await createClient()
  const org = await getAppOrg(supabase)

  if (!org) {
    return (
      <main>
        <div className="page-head">
          <h1>Projects</h1>
        </div>
        <p className="error">Bowden Works organization not found.</p>
      </main>
    )
  }

  const eu = await getEffectiveUser(supabase, org.id)
  const canEdit = canManageImports(eu)
  const userCanTransfer = canTransfer(eu)
  const viewAsScope = await getViewAsImportScope(supabase, org.id, eu)

  const {
    startBound,
    endBound,
    status,
    batch,
    q,
    project,
    user,
    source_email,
    client,
    operator,
    imported_by,
  } = readFiltersFromSearchParams(sp, {
    date: 'all',
    status: 'all',
  })

  // /projects-specific: "missing info" boolean flag.
  const missingInfo = sp.missing_info === '1'

  // View-as overrides any URL imported_by so a Rian-as-Adi session can't
  // sneak a Rian-only filter through.
  const effectiveImportedBy = viewAsScope
    ? eu!.effective_user_id
    : imported_by

  const rpcArgs = {
    p_org_id: org.id,
    p_start: startBound,
    p_end: endBound,
    p_status: status,
    p_batch: batch,
    p_q: q,
    p_project: project,
    p_user: user,
    p_source_email: source_email,
    p_client: client,
    p_operator: operator,
    p_imported_by: effectiveImportedBy,
    p_missing_info: missingInfo,
  }

  // Batches dropdown — same as /entries.
  let batchRowsQuery = supabase
    .from('clockify_imports')
    .select('id, name, filename, imported_at')
    .eq('org_id', org.id)
    .order('imported_at', { ascending: false })
  if (viewAsScope) batchRowsQuery = batchRowsQuery.in('id', viewAsScope)
  const { data: batchRows } = await batchRowsQuery.returns<
    {
      id: string
      name: string | null
      filename: string | null
      imported_at: string
    }[]
  >()
  const batchOptions = (batchRows ?? []).map((b) => ({
    id: b.id,
    label:
      b.name ??
      b.filename ??
      new Date(b.imported_at).toISOString().slice(0, 10),
  }))

  // Importers dropdown — owner-only, mirrors /entries.
  let importerOptions: { id: string; label: string }[] | null = null
  if (userCanTransfer && !viewAsScope) {
    const { data: rawImports } = await supabase
      .from('clockify_imports')
      .select('imported_by')
      .eq('org_id', org.id)
      .not('imported_by', 'is', null)
      .returns<{ imported_by: string }[]>()
    const importerIds = Array.from(
      new Set((rawImports ?? []).map((r) => r.imported_by)),
    )
    if (importerIds.length > 0) {
      const { data: profiles } = await supabase
        .from('profiles')
        .select('id, full_name, email')
        .in('id', importerIds)
        .returns<{ id: string; full_name: string | null; email: string }[]>()
      importerOptions = (profiles ?? [])
        .map((p) => ({ id: p.id, label: p.full_name || p.email }))
        .sort((a, b) => a.label.localeCompare(b.label))
    } else {
      importerOptions = []
    }
  }

  // Pivot options for the FilterBar — re-use the entries RPC, which
  // returns distinct values within the current filter scope.
  const optionsResult = await supabase.rpc('entries_filter_options', {
    p_org_id: org.id,
    p_start: startBound,
    p_end: endBound,
    p_status: status,
    p_batch: batch,
    p_q: q,
    p_project: project,
    p_user: user,
    p_source_email: source_email,
    p_client: client,
    p_operator: operator,
    p_imported_by: effectiveImportedBy,
  })
  const optionsRow = (optionsResult.data as OptionsRow[] | null)?.[0]
  const filterOptions: FilterOptions = {
    operators: optionsRow?.operators ?? [],
    clients: optionsRow?.clients ?? [],
    projects: optionsRow?.projects ?? [],
    users: optionsRow?.users ?? [],
    source_emails: optionsRow?.source_emails ?? [],
  }

  // The project_summary RPC. We sort in JS because the RPC currently
  // doesn't return rows in any guaranteed order (group-by output) —
  // doing it client-side also lets us sort by the derived hour values
  // without complicating the SQL.
  const summaryResult = await supabase.rpc('project_summary', rpcArgs)
  const summaryRows = (summaryResult.data as SummaryRpcRow[] | null) ?? []

  // Owner-only: load rate overrides + the team_member roster so the
  // /projects table can render an Overrides panel per project.
  type OverrideRow = {
    id: string
    project_id: string
    team_member_id: string | null
    override_rate_usd: number | string | null
    override_pct: number | string | null
    team_member: { display_name: string; email: string } | null
  }
  type TeamMemberOption = {
    id: string
    display_name: string
    email: string
    team: { name: string | null } | null
  }
  let overridesByProjectId = new Map<string, OverrideRow[]>()
  let teamMemberOptions: TeamMemberOption[] = []
  if (userCanTransfer) {
    const [{ data: rawOverrides }, { data: rawTms }] = await Promise.all([
      supabase
        .from('project_rate_overrides')
        .select(
          'id, project_id, team_member_id, override_rate_usd, override_pct, team_member:team_members(display_name, email)',
        )
        .eq('org_id', org.id)
        .returns<OverrideRow[]>(),
      supabase
        .from('team_members')
        .select('id, display_name, email, team:teams(name)')
        .eq('org_id', org.id)
        .order('display_name')
        .returns<TeamMemberOption[]>(),
    ])
    for (const o of rawOverrides ?? []) {
      const list = overridesByProjectId.get(o.project_id) ?? []
      list.push(o)
      overridesByProjectId.set(o.project_id, list)
    }
    teamMemberOptions = rawTms ?? []
  }
  const summaryError = summaryResult.error

  const projects: ProjectRow[] = summaryRows.map((r) => ({
    operator: r.operator,
    client: r.client,
    project: r.project,
    projectEntityId: r.project_entity_id ?? null,
    rowCount: Number(r.row_count ?? 0),
    billoutSeconds: Number(r.billout_seconds ?? 0),
    cost: Number(r.cost_usd ?? 0),
    billoutAmount:
      r.billout_amount_usd == null ? null : Number(r.billout_amount_usd),
    billoutAdjusted:
      r.billout_adjusted_usd == null
        ? null
        : Number(r.billout_adjusted_usd),
    billoutAdjPct:
      r.billout_adjustment_pct == null
        ? null
        : Number(r.billout_adjustment_pct),
    billoutAdjAmount:
      r.billout_adjustment_amount == null
        ? null
        : Number(r.billout_adjustment_amount),
    income: r.income_usd == null ? null : Number(r.income_usd),
  }))

  // Sort. NULL income / margin sort to the bottom regardless of dir so
  // unset-income rows don't crowd the top in the common "sort by margin
  // desc" view.
  const margin = (p: ProjectRow) =>
    p.income == null ? null : p.income - p.cost
  projects.sort((a, b) => {
    const dir = sortDir === 'asc' ? 1 : -1
    switch (sortKey) {
      case 'operator':
        return dir * (a.operator ?? '').localeCompare(b.operator ?? '')
      case 'client':
        return dir * (a.client ?? '').localeCompare(b.client ?? '')
      case 'project':
        return dir * (a.project ?? '').localeCompare(b.project ?? '')
      case 'row_count':
        return dir * (a.rowCount - b.rowCount)
      case 'billout_hrs':
        return dir * (a.billoutSeconds - b.billoutSeconds)
      case 'cost':
        return dir * (a.cost - b.cost)
      case 'billout_amount': {
        const va = a.billoutAmount ?? 0
        const vb = b.billoutAmount ?? 0
        return dir * (va - vb)
      }
      case 'income':
      case 'margin': {
        const va = sortKey === 'income' ? a.income : margin(a)
        const vb = sortKey === 'income' ? b.income : margin(b)
        if (va == null && vb == null) return 0
        if (va == null) return 1
        if (vb == null) return -1
        return dir * (va - vb)
      }
      default:
        return 0
    }
  })

  // Roll-up totals for the summary card. `income` and `margin` only
  // accumulate over projects with income set — keeps the % math meaningful.
  const totals = projects.reduce(
    (acc, p) => ({
      projectCount: acc.projectCount + 1,
      rowCount: acc.rowCount + p.rowCount,
      billoutSeconds: acc.billoutSeconds + p.billoutSeconds,
      cost: acc.cost + p.cost,
      income: acc.income + (p.income ?? 0),
      incomeRows: acc.incomeRows + (p.income == null ? 0 : 1),
      costForIncomed: acc.costForIncomed + (p.income == null ? 0 : p.cost),
    }),
    {
      projectCount: 0,
      rowCount: 0,
      billoutSeconds: 0,
      cost: 0,
      income: 0,
      incomeRows: 0,
      costForIncomed: 0,
    },
  )
  const totalMargin = userCanTransfer
    ? totals.income - totals.costForIncomed
    : null

  const filterSnapshot: FilterSnapshot = {
    date: typeof sp.date === 'string' ? sp.date : undefined,
    start: typeof sp.start === 'string' ? sp.start : undefined,
    end: typeof sp.end === 'string' ? sp.end : undefined,
    status: typeof sp.status === 'string' ? sp.status : undefined,
    batch: typeof sp.batch === 'string' ? sp.batch : undefined,
    q: typeof sp.q === 'string' ? sp.q : undefined,
    project: typeof sp.project === 'string' ? sp.project : undefined,
    user: typeof sp.user === 'string' ? sp.user : undefined,
    source_email:
      typeof sp.source_email === 'string' ? sp.source_email : undefined,
    client: typeof sp.client === 'string' ? sp.client : undefined,
    operator: typeof sp.operator === 'string' ? sp.operator : undefined,
    imported_by:
      typeof sp.imported_by === 'string' ? sp.imported_by : undefined,
    missing_info: missingInfo ? '1' : undefined,
  }

  return (
    <main>
      <div className="page-head">
        <div>
          <h1>Projects</h1>
          <p className="subtitle">
            One row per (operator, client, project). Sums respect the
            filter — change the filter and the totals follow. Click a
            project to see its entries grouped by description.{' '}
            {canEdit && (
              <Link href="/projects/manage" className="muted">
                Manage entities →
              </Link>
            )}
          </p>
        </div>
      </div>

      {error && <p className="error">{error}</p>}
      {info && <p className="info">{info}</p>}
      {summaryError && <p className="error">{summaryError.message}</p>}
      {bulkEdited != null && (
        <p className="info">
          ✓ Bulk-updated {bulkEdited}{' '}
          {bulkEdited === 1 ? 'entry' : 'entries'} across selected projects.
        </p>
      )}

      <QuickFilters />

      <FilterBar
        showStatus
        showBatch
        showQuery
        showPivots
        showClear
        defaultDate="all"
        defaultStatus="all"
        batches={batchOptions}
        options={filterOptions}
        importers={importerOptions}
      />

      <div className="panel summary-headline">
        <span className="summary-cost">${totals.cost.toFixed(2)} cost</span>
        <span className="summary-hours">
          {Math.floor(totals.billoutSeconds / 3600)}h{' '}
          {Math.floor((totals.billoutSeconds % 3600) / 60)
            .toString()
            .padStart(2, '0')}
          m
        </span>
        {userCanTransfer && totals.incomeRows > 0 && (
          <>
            <span className="summary-cost" style={{ color: '#0a7d3f' }}>
              ${totals.income.toFixed(2)} income
            </span>
            <span
              className={`summary-target ${
                (totalMargin ?? 0) >= 0 ? 'over-target' : 'under-target'
              }`}
            >
              {(totalMargin ?? 0) >= 0 ? '+' : ''}${
                (totalMargin ?? 0).toFixed(2)
              }{' '}
              margin ({totals.incomeRows}/{totals.projectCount} with income)
            </span>
          </>
        )}
        <span className="summary-target">
          {totals.projectCount} project{totals.projectCount === 1 ? '' : 's'} ·{' '}
          {totals.rowCount} entr{totals.rowCount === 1 ? 'y' : 'ies'}
        </span>
      </div>

      {projects.length === 0 ? (
        <p className="empty">No projects match this filter.</p>
      ) : (
        <ProjectsTable
          rows={projects}
          canEdit={canEdit}
          canSeeIncome={userCanTransfer}
          filter={filterSnapshot}
          expandKeys={expandKeys}
          sortKey={sortKey}
          sortDir={sortDir}
          editKey={typeof sp.edit_income === 'string' ? sp.edit_income : null}
          editRatesKey={
            typeof sp.edit_rates === 'string' ? sp.edit_rates : null
          }
          overridesByProjectId={Object.fromEntries(
            Array.from(overridesByProjectId.entries()).map(([pid, list]) => [
              pid,
              list.map((o) => ({
                id: o.id,
                team_member_id: o.team_member_id,
                rate:
                  o.override_rate_usd == null
                    ? null
                    : Number(o.override_rate_usd),
                pct:
                  o.override_pct == null ? null : Number(o.override_pct),
                team_member_name: o.team_member?.display_name ?? null,
                team_member_email: o.team_member?.email ?? null,
              })),
            ]),
          )}
          teamMemberOptions={teamMemberOptions.map((m) => ({
            id: m.id,
            label: m.display_name,
            email: m.email,
            team: m.team?.name ?? null,
          }))}
        />
      )}
    </main>
  )
}
