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

import { useGateways } from "@/app/di";
import type {
  Agent,
  Capability,
  PermissionEffect,
  PermissionPosture,
  PermissionRule,
  PermissionSet,
  ProjectPermissions,
} 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;
}

export interface PermissionsViewModel {
  agents: Agent[];
  rows: AgentPermissionRow[];
  document: ProjectPermissions | 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>;
}

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 [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const refresh = useCallback(async () => {
    setBusy(true);
    setError(null);
    try {
      const [agentList, permissionDoc] = await Promise.all([
        agent.listAgents(projectId),
        permission.getProjectPermissions(projectId),
      ]);
      setAgents(agentList);
      setDocument(permissionDoc);
    } 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]),
    );
    return agents.map((candidate) => ({
      agent: candidate,
      override: overrides.get(candidate.id) ?? null,
    }));
  }, [agents, document]);

  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],
  );

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