import { fetchViewableFile } from "@/services/files.service";
import { api } from "@/services/api";
import {
  companiesService,
  type CompanyProfileResponse,
  type LicenseFileStatus,
} from "@/services/companies.service";
import { getMinFiles } from "@/types/fileUploadSettings";
import type { ProfileResponse } from "@/types/profile";
import {
  SmartFileInput,
  useFileViewer,
  type ViewableFile,
} from "@edr/ui-common";
import {
  ActionIcon,
  Alert,
  Anchor,
  Badge,
  Button,
  Card,
  Center,
  Group,
  Stack,
  Text,
  Title,
  Tooltip,
} from "@mantine/core";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
  AlertTriangle,
  ArrowRight,
  CheckCircle2,
  Clock,
  FileCheck,
  FileText,
  Loader2,
  Paperclip,
  RefreshCw,
  Trash2,
  UploadCloud,
  XCircle,
} from "lucide-react";
import { useMemo, useRef, useState } from "react";

const ROLE_LABELS: Record<string, string> = {
  importer: "Importer",
  exporter: "Exporter",
  freight_forwarder: "Freight Forwarder",
  dj_freight_forwarder: "DJ Freight Forwarder",
  transporter: "Transporter",
};

interface TabDocumentsProps {
  profile: ProfileResponse;
  mode?: "edit" | "onboarding";
  onContinue?: () => void;
}

function documentSettingCode(nationality: string | null | undefined): string {
  return nationality === "foreign"
    ? "company_onboarding_documents_foreign"
    : "company_onboarding_documents_ethiopian";
}

/**
 * The delegation letter ships in the same nationality document set, but it is
 * edited on the Power of Attorney tab (where it is staged for review alongside
 * the PoA details), so it is excluded from this tab's uploader.
 */
const POA_DELEGATION_FILE_KEY = "poa_delegation_letter";

export default function TabDocuments({
  profile,
  mode = "edit",
  onContinue,
}: TabDocumentsProps) {
  const queryClient = useQueryClient();
  const { view, viewer } = useFileViewer();
  const [documentFiles, setDocumentFiles] = useState<
    Record<string, File | File[] | null>
  >({});

  const docSettingQuery = useQuery(
    api.fileUploadSettings.getByCode.queryOptions({
      input: { code: documentSettingCode(profile.nationality) },
    }),
  );

  const docSetting = useMemo(() => {
    const setting = docSettingQuery.data;
    if (!setting) return setting;
    return {
      ...setting,
      fields: setting.fields.filter(
        (f) => f.fileKey !== POA_DELEGATION_FILE_KEY,
      ),
    };
  }, [docSettingQuery.data]);

  const docsQuery = useQuery(
    api.companies.documents.queryOptions({
      input: { companyId: profile.companyId },
    }),
  );

  const uploadedKeys = useMemo(
    () => (docsQuery.data ?? []).map((d) => d.code),
    [docsQuery.data],
  );

  const existingFilesByKey = useMemo(() => {
    const map: Record<
      string,
      { name: string; url: string; size?: number; mimeType?: string | null }[]
    > = {};
    for (const doc of docsQuery.data ?? []) {
      // GET /api/files/:id is JWT-guarded, so no raw URL is stored here — the
      // file id rides in `url` and onViewFile resolves it to a blob URL.
      (map[doc.code] ??= []).push({
        name: doc.name,
        url: doc.id,
        size: doc.size,
        mimeType: doc.mimeType,
      });
    }
    return map;
  }, [docsQuery.data]);

  const docUploadMutation = useMutation({
    mutationFn: (files: Record<string, File | File[] | null>) =>
      companiesService.uploadDocuments(profile.companyId, files),
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: api.companies.getProfile.queryKey(),
      });
    },
  });

  const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});

  const handleFilesChange = (next: Record<string, File | File[] | null>) => {
    setDocumentFiles(next);
    // Clear required-field errors for any field that now has a file.
    setFieldErrors((prev) => {
      if (Object.keys(prev).length === 0) return prev;
      const updated = { ...prev };
      for (const key of Object.keys(updated)) {
        const v = next[key];
        const hasValue = Array.isArray(v) ? v.length > 0 : v != null;
        if (hasValue) delete updated[key];
      }
      return updated;
    });
  };

  // Array-aware: an emptied multi-file field is `[]`, which must not count.
  const hasFiles = Object.values(documentFiles).some((f) =>
    Array.isArray(f) ? f.length > 0 : f != null,
  );

  const validateRequired = (): Record<string, string> => {
    const errs: Record<string, string> = {};
    for (const field of docSetting?.fields ?? []) {
      const min = getMinFiles(field);
      if (min <= 0) continue;
      if (uploadedKeys.includes(field.fileKey)) continue;
      const v = documentFiles[field.fileKey];
      const count = Array.isArray(v) ? v.length : v ? 1 : 0;
      if (count < min) {
        errs[field.fileKey] = `${field.fileLabel} is required`;
      }
    }
    return errs;
  };

  const licenseProfiles = profile.companyProfiles;

  // Company documents render through SmartFileInput, which is keyed by field and
  // has no per-file review slot. Surfacing the outstanding corrections as one
  // banner keeps the reviewer's notes visible without reshaping that component.
  const changeRequested = useMemo(
    () =>
      (docsQuery.data ?? []).filter(
        (d) => d.reviewStatus === "change_requested",
      ),
    [docsQuery.data],
  );

  return (
    <>
      <Card padding="lg">
        <Group gap="sm" mb="xs">
          <FileCheck size={20} />
          <Title order={3}>Documents</Title>
        </Group>
        <Text c="edr-muted" size="sm" mb="lg">
          Upload and manage required business documents
        </Text>

        {changeRequested.length > 0 && (
          <Alert
            color="red"
            variant="light"
            radius="md"
            mb="lg"
            icon={<AlertTriangle size={18} />}
            title="Some documents need correcting"
          >
            <Stack gap={6}>
              <Text size="sm">
                Re-upload the documents below. Your account cannot be approved
                until they are corrected.
              </Text>
              {changeRequested.map((d) => (
                <Text key={d.id} size="sm">
                  <strong>{d.name}</strong>
                  {d.reviewNote ? ` — ${d.reviewNote}` : ""}
                </Text>
              ))}
            </Stack>
          </Alert>
        )}

        {docSettingQuery.isLoading ? (
          <Center py="xl">
            <Loader2 size={24} className="animate-spin" />
          </Center>
        ) : !docSetting ? (
          <Text c="edr-muted" size="sm" ta="center" py="md">
            No document requirements configured for your account.
          </Text>
        ) : (
          <SmartFileInput
            file={docSetting}
            value={documentFiles}
            onChange={handleFilesChange}
            errors={fieldErrors}
            uploadedKeys={uploadedKeys}
            existingFiles={existingFilesByKey}
            onViewFile={(f) => void fetchViewableFile(f.url, f.name).then(view)}
          />
        )}

        {docSetting && (
          <Group
            justify="space-between"
            mt="lg"
            pt="md"
            style={{ borderTop: "1px solid var(--mantine-color-edr-border-0)" }}
          >
            <Group gap="xs">
              {docUploadMutation.isSuccess && (
                <Group gap={6} c="green">
                  <CheckCircle2 size={16} />
                  <Text size="sm" fw={500}>
                    {mode === "onboarding"
                      ? "Saved successfully"
                      : "Documents uploaded successfully"}
                  </Text>
                </Group>
              )}
              {docUploadMutation.isError && (
                <Group gap={6} c="red">
                  <XCircle size={16} />
                  <Text size="sm" fw={500}>
                    Upload failed
                  </Text>
                </Group>
              )}
            </Group>
            {mode === "onboarding" ? (
              <Button
                type="button"
                leftSection={<ArrowRight size={16} />}
                loading={docUploadMutation.isPending}
                onClick={() => {
                  const validationErrors = validateRequired();
                  if (Object.keys(validationErrors).length > 0) {
                    setFieldErrors(validationErrors);
                    return;
                  }
                  if (hasFiles) {
                    docUploadMutation.mutate(documentFiles, {
                      onSuccess: () => onContinue?.(),
                    });
                  } else {
                    onContinue?.();
                  }
                }}
              >
                Continue
              </Button>
            ) : (
              <Button
                type="button"
                leftSection={<UploadCloud size={16} />}
                loading={docUploadMutation.isPending}
                disabled={!hasFiles}
                onClick={() => {
                  if (!hasFiles) return;
                  docUploadMutation.mutate(documentFiles);
                }}
              >
                Upload Documents
              </Button>
            )}
          </Group>
        )}
      </Card>

      {licenseProfiles.length > 0 && (
        <Card padding="lg" mt="lg">
          <Group gap="sm" mb="xs">
            <Paperclip size={20} />
            <Title order={3}>Business licenses</Title>
          </Group>
          <Text c="edr-muted" size="sm" mb="lg">
            Add, replace or remove the license documents for each operational
            profile. Changes are submitted to EDR for review before they take
            effect.
          </Text>

          <Stack gap="xl">
            {licenseProfiles.map((p) => (
              <ProfileLicenseRow
                key={p.id}
                profile={p}
                onViewFile={view}
                reviewPending={profile.reviewStatus === "pending"}
              />
            ))}
          </Stack>
        </Card>
      )}

      {viewer}
    </>
  );
}

const LICENSE_ACCEPT = ".pdf,.png,.jpg,.jpeg";

function formatBytes(bytes: number): string {
  if (!bytes) return "";
  const units = ["B", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(1))} ${units[i]}`;
}

const STATUS_BADGE: Record<
  LicenseFileStatus,
  { label: string; color: string; bg: string; fg: string } | null
> = {
  live: null,
  pending_add: {
    label: "Pending approval",
    color: "edr-amber",
    bg: "var(--mantine-color-edr-amber-soft-0)",
    fg: "var(--mantine-color-edr-amber-text-0)",
  },
  pending_remove: {
    label: "Removal pending",
    color: "edr-red",
    bg: "var(--mantine-color-edr-red-soft-0)",
    fg: "var(--mantine-color-edr-red-0)",
  },
};

/**
 * One operational profile's business-license documents. Lists each file (click
 * to preview via the file proxy) with its review state, and lets the customer
 * add / replace / remove files. Every mutation opens a change request the
 * backoffice must approve; while one is open the parent locks this whole tab.
 */
function ProfileLicenseRow({
  profile,
  onViewFile,
  reviewPending,
}: {
  profile: CompanyProfileResponse;
  onViewFile: (file: ViewableFile) => void;
  reviewPending: boolean;
}) {
  const queryClient = useQueryClient();
  const addInputRef = useRef<HTMLInputElement>(null);
  const replaceInputRef = useRef<HTMLInputElement>(null);
  const replaceTargetId = useRef<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const invalidate = () => {
    setError(null);
    queryClient.invalidateQueries({
      queryKey: api.companies.getProfile.queryKey(),
    });
  };

  const addMutation = useMutation({
    mutationFn: (files: File[]) =>
      companiesService.uploadProfileLicense(profile.id, files),
    onSuccess: invalidate,
    onError: () => setError("Upload failed. Please try again."),
  });
  const replaceMutation = useMutation({
    mutationFn: ({ fileId, file }: { fileId: string; file: File }) =>
      companiesService.replaceProfileLicense(profile.id, fileId, file),
    onSuccess: invalidate,
    onError: () => setError("Replace failed. Please try again."),
  });
  const removeMutation = useMutation({
    mutationFn: (fileId: string) =>
      companiesService.removeProfileLicense(profile.id, fileId),
    onSuccess: invalidate,
    onError: () => setError("Remove failed. Please try again."),
  });

  const busy =
    addMutation.isPending ||
    replaceMutation.isPending ||
    removeMutation.isPending;
  const files = profile.licenseFiles ?? [];

  return (
    <Stack gap="sm">
      <Group justify="space-between" align="center">
        <Text size="sm" fw={700} c="edr-text">
          {ROLE_LABELS[profile.type] ?? profile.type}
          {profile.reference ? ` · ${profile.reference}` : ""}
        </Text>
        <Button
          variant="light"
          size="xs"
          leftSection={<UploadCloud size={14} />}
          loading={addMutation.isPending}
          disabled={busy}
          onClick={() => addInputRef.current?.click()}
        >
          Add document
        </Button>
      </Group>

      {files.length === 0 ? (
        <Card
          padding="md"
          radius="md"
          style={{
            borderStyle: "dashed",
            backgroundColor: "var(--mantine-color-edr-bg-0)",
          }}
        >
          <Text size="sm" c="edr-muted" ta="center">
            No license documents yet.
          </Text>
        </Card>
      ) : (
        <Stack gap="xs">
          {files.map((f) => {
            const badge = STATUS_BADGE[f.status];
            const isPending = f.status !== "live";
            return (
              <Card
                key={f.id}
                padding="sm"
                radius="md"
                withBorder
                style={{ backgroundColor: "var(--mantine-color-edr-card-0)" }}
              >
                <Group gap="sm" wrap="nowrap">
                  <FileText
                    size={18}
                    className="text-edr-muted"
                    style={{ flexShrink: 0 }}
                  />
                  <Stack gap={0} style={{ minWidth: 0, flex: 1 }}>
                    <Anchor
                      component="button"
                      type="button"
                      size="sm"
                      fw={600}
                      onClick={() =>
                        void fetchViewableFile(f.id, f.name).then(onViewFile)
                      }
                      style={{
                        textAlign: "left",
                        textDecoration:
                          f.status === "pending_remove"
                            ? "line-through"
                            : undefined,
                      }}
                      lineClamp={1}
                    >
                      {f.name}
                    </Anchor>
                    {f.size > 0 && (
                      <Text size="xs" c="edr-muted">
                        {formatBytes(f.size)}
                      </Text>
                    )}
                    {/* Without the reason the badge is unactionable — the
                        customer would not know what to change. */}
                    {f.reviewStatus === "change_requested" && f.reviewNote && (
                      <Text size="xs" c="red" mt={2}>
                        {f.reviewNote}
                      </Text>
                    )}
                  </Stack>

                  {/* A reviewer's correction request outranks the staged-file
                      badge: it is the one state the customer must act on. */}
                  {f.reviewStatus === "change_requested" ? (
                    <Badge
                      size="sm"
                      radius="sm"
                      variant="light"
                      leftSection={<AlertTriangle size={11} />}
                      style={{
                        backgroundColor:
                          "var(--mantine-color-edr-red-soft-0)",
                        color: "var(--mantine-color-edr-red-0)",
                        flexShrink: 0,
                      }}
                    >
                      Change requested
                    </Badge>
                  ) : (
                    badge && (
                      <Badge
                        size="sm"
                        radius="sm"
                        variant="light"
                        leftSection={<Clock size={11} />}
                        style={{
                          backgroundColor: badge.bg,
                          color: badge.fg,
                          flexShrink: 0,
                        }}
                      >
                        {badge.label}
                      </Badge>
                    )
                  )}

                  <Tooltip label="Replace" withArrow>
                    <ActionIcon
                      variant="subtle"
                      color="gray"
                      aria-label={`Replace ${f.name}`}
                      disabled={busy || isPending}
                      onClick={() => {
                        replaceTargetId.current = f.id;
                        replaceInputRef.current?.click();
                      }}
                    >
                      <RefreshCw size={15} />
                    </ActionIcon>
                  </Tooltip>
                  <Tooltip label="Remove" withArrow>
                    <ActionIcon
                      variant="subtle"
                      color="red"
                      aria-label={`Remove ${f.name}`}
                      disabled={busy || isPending}
                      loading={
                        removeMutation.isPending &&
                        removeMutation.variables === f.id
                      }
                      onClick={() => removeMutation.mutate(f.id)}
                    >
                      <Trash2 size={15} />
                    </ActionIcon>
                  </Tooltip>
                </Group>
              </Card>
            );
          })}
        </Stack>
      )}

      {reviewPending && (
        <Group gap={6} c="edr-amber-text">
          <Clock size={13} />
          <Text size="xs" fw={500}>
            Awaiting EDR review — further changes are disabled until it clears.
          </Text>
        </Group>
      )}
      {error && (
        <Group gap={6} c="red">
          <XCircle size={13} />
          <Text size="xs" fw={500}>
            {error}
          </Text>
        </Group>
      )}

      <input
        ref={addInputRef}
        type="file"
        multiple
        accept={LICENSE_ACCEPT}
        style={{ display: "none" }}
        onChange={(e) => {
          const picked = e.target.files ? Array.from(e.target.files) : [];
          if (picked.length > 0) addMutation.mutate(picked);
          e.target.value = "";
        }}
      />
      <input
        ref={replaceInputRef}
        type="file"
        accept={LICENSE_ACCEPT}
        style={{ display: "none" }}
        onChange={(e) => {
          const file = e.target.files?.[0];
          const fileId = replaceTargetId.current;
          if (file && fileId) replaceMutation.mutate({ fileId, file });
          replaceTargetId.current = null;
          e.target.value = "";
        }}
      />
    </Stack>
  );
}
