import { useCallback, useEffect, useMemo, useState } from "react";

import { useGateways } from "@/app/di";
import type {
  Agent,
  Capability,
  PermissionEffect,
  PermissionPosture,
  PermissionRule,
  PermissionSet,
  ProjectPermissions,
  ProjectSystemPermissions,
  ResolvedAgentSystemPermissions,
  SystemPermissionSet,
} from "@/domain";

export type CapabilityChoice = "none" | PermissionEffect;

export interface PolicyDraft {
  fallback: PermissionPosture;
  read: CapabilityChoice;
  write: CapabilityChoice;
  delete: CapabilityChoice;
  executeBash: CapabilityChoice;
}

export interface AgentPermissionRow {
  agent: Agent;
  override: PermissionSet | null;
  systemOverride: SystemPermissionSet | null;
  resolvedSystem: ResolvedAgentSystemPermissions | null;
}

export interface PermissionsViewModel {
  agents: Agent[];
  rows: AgentPermissionRow[];
  document: ProjectPermissions | null;
  systemDocument: ProjectSystemPermissions | null;
  projectDraft: PolicyDraft;
  busy: boolean;
  error: string | null;
  refresh: () => Promise<void>;
  saveProjectDefaults: (draft: PolicyDraft) => Promise<void>;
  clearProjectDefaults: () => Promise<void>;
  saveAgentOverride: (agentId: string, draft: PolicyDraft) => Promise<void>;
  clearAgentOverride: (agentId: string) => Promise<void>;
  saveProjectSystemPermissions: (
    permissions: SystemPermissionSet | null,
  ) => Promise<void>;
  saveAgentSystemPermissions: (
    agentId: string,
    permissions: SystemPermissionSet | null,
  ) => Promise<void>;
}

const DEFAULT_DRAFT: PolicyDraft = {
  fallback: "ask",
  read: "none",
  write: "none",
  delete: "none",
  executeBash: "none",
};

const CAPABILITIES: Capability[] = ["read", "write", "delete", "executeBash"];

function describe(e: unknown): string {
  if (e && typeof e === "object" && "message" in e) {
    return String((e as { message: unknown }).message);
  }
  return String(e);
}

export function draftFromSet(set?: PermissionSet | null): PolicyDraft {
  if (!set) return { ...DEFAULT_DRAFT };
  const draft: PolicyDraft = {
    ...DEFAULT_DRAFT,
    fallback: set.fallback,
  };
  for (const capability of CAPABILITIES) {
    draft[capability] = ruleChoice(set.rules, capability);
  }
  return draft;
}

function ruleChoice(
  rules: PermissionRule[],
  capability: Capability,
): CapabilityChoice {
  const matching = rules.filter((rule) => rule.capability === capability);
  if (matching.some((rule) => rule.effect === "deny")) return "deny";
  if (matching.some((rule) => rule.effect === "allow")) return "allow";
  return "none";
}

export function setFromDraft(draft: PolicyDraft): PermissionSet {
  const rules: PermissionRule[] = [];
  for (const capability of CAPABILITIES) {
    const effect = draft[capability];
    if (effect === "none") continue;
    rules.push(ruleFromChoice(capability, effect));
  }
  return { fallback: draft.fallback, rules };
}

function ruleFromChoice(
  capability: Capability,
  effect: PermissionEffect,
): PermissionRule {
  if (capability === "executeBash") {
    return { capability, effect, commands: [] };
  }
  return { capability, effect, paths: ["**"] };
}

export function isCustomPolicy(set?: PermissionSet | null): boolean {
  if (!set) return false;
  return set.rules.some((rule) => {
    if (rule.capability === "executeBash") {
      return (rule.commands ?? []).length > 0;
    }
    const paths = rule.paths ?? [];
    return paths.length !== 1 || paths[0] !== "**";
  });
}

export function usePermissions(projectId: string): PermissionsViewModel {
  const { agent, permission } = useGateways();
  const [agents, setAgents] = useState<Agent[]>([]);
  const [document, setDocument] = useState<ProjectPermissions | null>(null);
  const [systemDocument, setSystemDocument] =
    useState<ProjectSystemPermissions | null>(null);
  const [resolvedSystemByAgent, setResolvedSystemByAgent] = useState<
    Record<string, ResolvedAgentSystemPermissions>
  >({});
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const refresh = useCallback(async () => {
    setBusy(true);
    setError(null);
    try {
      const [agentList, permissionDoc, systemPermissionDoc] = await Promise.all([
        agent.listAgents(projectId),
        permission.getProjectPermissions(projectId),
        permission.getProjectSystemPermissions(projectId),
      ]);
      const resolvedPairs = await Promise.all(
        agentList.map(async (candidate) => {
          try {
            return [
              candidate.id,
              await permission.resolveAgentSystemPermissions(projectId, candidate.id),
            ] as const;
          } catch {
            return [candidate.id, null] as const;
          }
        }),
      );
      setAgents(agentList);
      setDocument(permissionDoc);
      setSystemDocument(systemPermissionDoc);
      setResolvedSystemByAgent(
        Object.fromEntries(
          resolvedPairs.filter(
            (pair): pair is readonly [string, ResolvedAgentSystemPermissions] =>
              pair[1] !== null,
          ),
        ),
      );
    } catch (e) {
      setError(describe(e));
    } finally {
      setBusy(false);
    }
  }, [agent, permission, projectId]);

  useEffect(() => {
    void refresh();
  }, [refresh]);

  const rows = useMemo<AgentPermissionRow[]>(() => {
    const overrides = new Map(
      (document?.agents ?? []).map((entry) => [entry.agentId, entry.permissions]),
    );
    const systemOverrides = new Map(
      (systemDocument?.agents ?? []).map((entry) => [
        entry.agentId,
        entry.permissions,
      ]),
    );
    return agents.map((candidate) => ({
      agent: candidate,
      override: overrides.get(candidate.id) ?? null,
      systemOverride: systemOverrides.get(candidate.id) ?? null,
      resolvedSystem: resolvedSystemByAgent[candidate.id] ?? null,
    }));
  }, [agents, document, systemDocument, resolvedSystemByAgent]);

  const projectDraft = useMemo(
    () => draftFromSet(document?.projectDefaults ?? null),
    [document],
  );

  const saveProjectDefaults = useCallback(
    async (draft: PolicyDraft) => {
      setBusy(true);
      setError(null);
      try {
        setDocument(
          await permission.updateProjectPermissions(projectId, setFromDraft(draft)),
        );
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [permission, projectId],
  );

  const clearProjectDefaults = useCallback(async () => {
    setBusy(true);
    setError(null);
    try {
      setDocument(await permission.updateProjectPermissions(projectId, null));
    } catch (e) {
      setError(describe(e));
    } finally {
      setBusy(false);
    }
  }, [permission, projectId]);

  const saveAgentOverride = useCallback(
    async (agentId: string, draft: PolicyDraft) => {
      setBusy(true);
      setError(null);
      try {
        setDocument(
          await permission.updateAgentPermissions(
            projectId,
            agentId,
            setFromDraft(draft),
          ),
        );
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [permission, projectId],
  );

  const clearAgentOverride = useCallback(
    async (agentId: string) => {
      setBusy(true);
      setError(null);
      try {
        setDocument(
          await permission.updateAgentPermissions(projectId, agentId, null),
        );
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [permission, projectId],
  );

  const saveProjectSystemPermissions = useCallback(
    async (permissionsToSave: SystemPermissionSet | null) => {
      setBusy(true);
      setError(null);
      try {
        const next = await permission.updateProjectSystemPermissions(
          projectId,
          permissionsToSave,
        );
        setSystemDocument(next);
        await refresh();
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [permission, projectId, refresh],
  );

  const saveAgentSystemPermissions = useCallback(
    async (agentId: string, permissionsToSave: SystemPermissionSet | null) => {
      setBusy(true);
      setError(null);
      try {
        const next = await permission.updateAgentSystemPermissions(
          projectId,
          agentId,
          permissionsToSave,
        );
        setSystemDocument(next);
        await refresh();
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [permission, projectId, refresh],
  );

  return {
    agents,
    rows,
    document,
    systemDocument,
    projectDraft,
    busy,
    error,
    refresh,
    saveProjectDefaults,
    clearProjectDefaults,
    saveAgentOverride,
    clearAgentOverride,
    saveProjectSystemPermissions,
    saveAgentSystemPermissions,
  };
}
