export type ServerStatus = "pending" | "active" | "offline";
export type KeySource = "uploaded" | "generated";

export interface PackageUpdate {
    name: string;
    current_version?: string;
    new_version: string;
}

export interface Inventory {
    cpu: { model?: string; cores?: number; usage_pct: number; load1?: number };
    memory: { total_bytes: number; used_bytes: number };
    swap_total_bytes: number;
    swap_used_bytes: number;
    partitions?: { device: string; mountpoint: string; fstype?: string; total_bytes: number; used_bytes: number }[];
    kernel?: string;
    metrics_at?: string;
    static_at?: string;
}

export interface Server {
    id: string;
    server_id: string;
    hostname: string;
    ip_address: string;
    os_info: string;
    status: ServerStatus;
    agent_version?: string;
    last_seen: string;
    created_at: string;
    available_updates?: PackageUpdate[];
    updates_checked_at?: string;
    console_protocols?: string[];
    inventory?: Inventory;
}

export type MonitorType = "http" | "tcp" | "icmp" | "tls";
export type MonitorStatus = "up" | "down" | "pending";

export interface MonitorTarget {
    url?: string;
    host?: string;
    port?: number;
    method?: string;
    expected_status?: number;
    keyword?: string;
    tls_warn_days?: number;
    insecure?: boolean;
}

export interface MonitorState {
    status: MonitorStatus;
    last_check_at?: string;
    latency_ms: number;
    message?: string;
    cert_expiry_at?: string;
    fails: number;
}

export interface Monitor {
    monitor_id: string;
    name: string;
    type: MonitorType;
    target: MonitorTarget;
    interval_sec: number;
    runner: string; // "server" or a server_id
    retries: number;
    enabled: boolean;
    channel_ids?: string[];
    state: MonitorState;
    created_at: string;
}

export interface MonitorInput {
    name: string;
    type: MonitorType;
    target: MonitorTarget;
    interval_sec: number;
    runner: string;
    retries: number;
    enabled: boolean;
    channel_ids?: string[];
}

export interface Incident {
    incident_id: string;
    monitor_id: string;
    started_at: string;
    resolved_at?: string;
    cause?: string;
}

export interface Rollup {
    monitor_id: string;
    period_start: string;
    checks: number;
    up_count: number;
    sum_latency: number;
}

export type ChannelType = "webhook" | "smtp" | "discord" | "slack" | "telegram";

export interface NotificationChannel {
    channel_id: string;
    name: string;
    type: ChannelType;
    config: Record<string, string>;
    enabled: boolean;
    created_at: string;
}

export interface ChannelInput {
    name: string;
    type: ChannelType;
    config: Record<string, string>;
    enabled: boolean;
}

export interface ConsoleConnectRequest {
    server_id: string;
    protocol: string;
    key_id?: string;
    rdp_username?: string;
    rdp_password?: string;
    ssh_username?: string;
}

export interface ConsoleConnectResponse {
    session_id: string;
    token: string;
    ws_path: string;
}

export interface Key {
    id: string;
    key_id: string;
    label: string;
    public_key: string;
    fingerprint: string;
    source: KeySource;
    generated_by_server_id?: string;
    has_private_key: boolean;
    has_passphrase?: boolean;
    created_at: string;
    assigned_count?: number;
}

export interface Assignment {
    id: string;
    key_id: string;
    server_id: string;
    assigned_at: string;
    revoked_at: string | null;
}

export interface AuditEvent {
    id: string;
    event_type: string;
    actor: string;
    server_id?: string;
    key_id?: string;
    details: string;
    created_at: string;
}

export interface AlertSettings {
    enabled: boolean;
    webhook_url: string;
    offline_threshold_minutes: number;
}

export interface EmailSettings {
    enabled: boolean;
    smtp_host: string;
    smtp_port: number;
    username: string;
    password: string;
    from_addr: string;
    to_addrs: string[];
    use_tls: boolean;
}

export interface SecretsSettings {
    read_token_set: boolean;
    rotated_at?: string;
}

export interface Settings {
    alerts: AlertSettings;
    email: EmailSettings;
    secrets: SecretsSettings;
    workflow_log_retention_days?: number | null;
}

export interface SecretGroupSummary {
    group: string;
    key_count: number;
    updated_at: string;
}

export interface Secret {
    group: string;
    key: string;
    updated_at: string;
}

export interface NewServerResponse {
    server_id: string;
    pre_reg_token: string;
    install_command: string;
    install_command_ps: string;
}

export interface GenerateKeyOptions {
    label: string;
    key_type: "ed25519" | "rsa" | "ecdsa";
    key_size?: number;
    passphrase?: string;
    comment?: string;
}

export interface KeyWithAssignments extends Key {
    assignments: (Assignment & { server: Server })[];
}

export interface ServerWithKeys extends Server {
    keys: (Assignment & { key: Key })[];
}

export interface InputParam {
    name: string;
    default: string;
    description: string;
}

export interface WorkflowStep {
    step_id: string;
    name: string;
    description: string;
    interpreter: "bash" | "powershell";
    script: string;
    declared_outputs: string[];
    declared_inputs: InputParam[];
    secret_refs: string[];
    source?: "user" | "default";
    slug?: string;
}

export interface WorkflowStepRef {
    step_id?: string;
    inline?: WorkflowStep;
    order: number;
    on_failure: "stop" | "continue" | "retry";
    max_retries: number;
    overrides?: { script?: string; secret_refs?: string[] };
    inputs?: Record<string, string>;
}

export interface Workflow {
    workflow_id: string;
    name: string;
    target_server_ids: string[];
    steps: WorkflowStepRef[];
}

export interface StepRun {
    order: number;
    name: string;
    status: string;
    attempts: number;
    exit_code: number;
    log_offset: number;
    output_env: Record<string, string>;
    started_at?: string;
    finished_at?: string;
}

export interface ServerRun {
    server_id: string;
    hostname: string;
    status: string;
    run_env: Record<string, string>;
    steps: StepRun[];
    started_at?: string;
    finished_at?: string;
}

export interface WorkflowRun {
    run_id: string;
    workflow_id: string;
    name: string;
    status: string;
    triggered_by: string;
    started_at: string;
    finished_at?: string;
    server_runs: ServerRun[];
}

export type Role = "owner" | "admin" | "member";

/** The session as returned by GET /auth/me mirrors auth.Session on the server. */
export interface SessionUser {
    user_id: string;
    instance_id: string;
    role: Role;
    email: string;
    name: string;
}

export interface Instance {
    instance_id: string;
    name: string;
    slug: string;
    created_at: string;
}

export interface MeResponse {
    user: SessionUser;
    instance: Instance | null;
}

export interface BootstrapStatus {
    needs_setup: boolean;
    instance_name?: string;
}

export interface BootstrapResponse {
    instance: Instance;
    slug: string;
    instance_id: string;
}

export interface InstanceUser {
    user_id: string;
    instance_id: string;
    email: string;
    role: Role;
    auth_source: "local" | "oidc";
    created_at: string;
    last_login?: string;
}

export interface OrgUserInput {
    email: string;
    password: string;
    role: Role;
}

export interface OrgOIDCConfig {
    issuer?: string;
    client_id?: string;
    enabled: boolean;
    client_secret_set: boolean;
    updated_at?: string;
}

export interface OrgOIDCInput {
    issuer: string;
    client_id: string;
    client_secret: string;
    enabled: boolean;
}

class ApiError extends Error {
    constructor(
        public status: number,
        message: string,
    ) {
        super(message);
        this.name = "ApiError";
    }
}

async function request<T>(path: string, options?: RequestInit): Promise<T> {
    const res = await fetch(`/api${path}`, {
        credentials: "include",
        headers: {
            "Content-Type": "application/json",
            ...options?.headers,
        },
        ...options,
    });

    if (!res.ok) {
        const text = await res.text().catch(() => "");

        let message = text || res.statusText || `HTTP ${res.status}`;
        try {
            const body = JSON.parse(text);
            if (body?.error) message = body.error;
        } catch {}
        throw new ApiError(res.status, message);
    }

    if (res.status === 204) {
        return undefined as T;
    }

    return res.json();
}

async function authRequest<T>(path: string, options?: RequestInit): Promise<T> {
    const res = await fetch(path, {
        credentials: "include",
        headers: { "Content-Type": "application/json", ...options?.headers },
        ...options,
    });

    if (!res.ok) {
        let message = `HTTP ${res.status}`;
        try {
            const body = await res.json();
            if (body?.error) message = body.error;
        } catch {}
        throw new ApiError(res.status, message);
    }

    if (res.status === 204) {
        return undefined as T;
    }

    return res.json();
}

export const auth = {
    bootstrapStatus(): Promise<BootstrapStatus> {
        return authRequest<BootstrapStatus>("/auth/bootstrap-status");
    },

    bootstrap(input: { instance_name: string; email: string; password: string }): Promise<BootstrapResponse> {
        return authRequest<BootstrapResponse>("/auth/bootstrap", {
            method: "POST",
            body: JSON.stringify(input),
        });
    },

    login(email: string, password: string): Promise<{ ok: boolean }> {
        return authRequest<{ ok: boolean }>("/auth/login", {
            method: "POST",
            body: JSON.stringify({ email, password }),
        });
    },

    logout(): Promise<void> {
        return authRequest<void>("/auth/logout", { method: "POST" });
    },

    me(): Promise<MeResponse> {
        return authRequest<MeResponse>("/auth/me");
    },

    /** The URL an admin must register with their OIDC provider. */
    oidcRedirectUrl(): string {
        if (typeof window === "undefined") return "/auth/oidc/callback";
        return `${window.location.origin}/auth/oidc/callback`;
    },
};

export const api = {
    listInstanceUsers(): Promise<InstanceUser[]> {
        return request<InstanceUser[]>("/instance/users");
    },

    createInstanceUser(input: OrgUserInput): Promise<InstanceUser> {
        return request<InstanceUser>("/instance/users", { method: "POST", body: JSON.stringify(input) });
    },

    updateInstanceUserRole(userId: string, role: Role): Promise<{ ok: boolean }> {
        return request<{ ok: boolean }>(`/instance/users/${userId}/role`, {
            method: "PUT",
            body: JSON.stringify({ role }),
        });
    },

    deleteInstanceUser(userId: string): Promise<void> {
        return request<void>(`/instance/users/${userId}`, { method: "DELETE" });
    },

    getInstanceOIDC(): Promise<OrgOIDCConfig> {
        return request<OrgOIDCConfig>("/instance/oidc");
    },

    saveInstanceOIDC(input: OrgOIDCInput): Promise<{ saved: boolean }> {
        return request<{ saved: boolean }>("/instance/oidc", { method: "PUT", body: JSON.stringify(input) });
    },

    listServers(): Promise<Server[]> {
        return request<Server[]>("/servers");
    },

    getServer(serverId: string): Promise<ServerWithKeys> {
        return request<ServerWithKeys>(`/servers/${serverId}`);
    },

    createServer(): Promise<NewServerResponse> {
        return request<NewServerResponse>("/servers/new", { method: "POST" });
    },

    deleteServer(serverId: string): Promise<void> {
        return request<void>(`/servers/${serverId}`, { method: "DELETE" });
    },

    generateKeyForServer(serverId: string, opts: GenerateKeyOptions): Promise<{ command_id: string }> {
        return request<{ command_id: string }>(`/servers/${serverId}/generate-key`, {
            method: "POST",
            body: JSON.stringify(opts),
        });
    },

    getUpdateCommand(osInfo?: string): string {
        if (osInfo && osInfo.toLowerCase().includes("windows")) {
            return `irm "${window.location.origin}/update.ps1" | iex`;
        }
        return `curl -fsSL "${window.location.origin}/update" | bash`;
    },

    listMonitors(): Promise<Monitor[]> {
        return request<Monitor[]>("/monitors");
    },

    getMonitor(monitorId: string): Promise<Monitor> {
        return request<Monitor>(`/monitors/${monitorId}`);
    },

    createMonitor(input: MonitorInput): Promise<Monitor> {
        return request<Monitor>("/monitors", { method: "POST", body: JSON.stringify(input) });
    },

    updateMonitor(monitorId: string, input: Partial<MonitorInput>): Promise<void> {
        return request<void>(`/monitors/${monitorId}`, { method: "PUT", body: JSON.stringify(input) });
    },

    deleteMonitor(monitorId: string): Promise<void> {
        return request<void>(`/monitors/${monitorId}`, { method: "DELETE" });
    },

    getMonitorIncidents(monitorId: string): Promise<Incident[]> {
        return request<Incident[]>(`/monitors/${monitorId}/incidents`);
    },

    getMonitorUptime(monitorId: string): Promise<Rollup[]> {
        return request<Rollup[]>(`/monitors/${monitorId}/uptime`);
    },

    listChannels(): Promise<NotificationChannel[]> {
        return request<NotificationChannel[]>("/channels");
    },

    createChannel(input: ChannelInput): Promise<NotificationChannel> {
        return request<NotificationChannel>("/channels", { method: "POST", body: JSON.stringify(input) });
    },

    updateChannel(channelId: string, input: Partial<ChannelInput>): Promise<void> {
        return request<void>(`/channels/${channelId}`, { method: "PUT", body: JSON.stringify(input) });
    },

    deleteChannel(channelId: string): Promise<void> {
        return request<void>(`/channels/${channelId}`, { method: "DELETE" });
    },

    testChannel(channelId: string): Promise<{ status: string }> {
        return request<{ status: string }>(`/channels/${channelId}/test`, { method: "POST" });
    },

    getLatestAgentVersion(): Promise<{ version: string }> {
        return request<{ version: string }>("/agent/latest-version");
    },

    updateAgent(serverId: string): Promise<{ message: string; version: string }> {
        return request<{ message: string; version: string }>(`/servers/${serverId}/update-agent`, {
            method: "POST",
        });
    },

    applyUpdates(serverId: string): Promise<{ message: string }> {
        return request<{ message: string }>(`/servers/${serverId}/apply-updates`, {
            method: "POST",
        });
    },

    listAuditEvents(limit?: number): Promise<AuditEvent[]> {
        const qs = limit ? `?limit=${limit}` : "";
        return request<AuditEvent[]>(`/audit${qs}`);
    },

    getSettings(): Promise<Settings> {
        return request<Settings>("/settings");
    },

    saveSettings(settings: { alerts: AlertSettings; email: EmailSettings; workflow_log_retention_days?: number | null }): Promise<{ saved: boolean }> {
        return request<{ saved: boolean }>("/settings", {
            method: "PUT",
            body: JSON.stringify(settings),
        });
    },

    rotateSecretsToken(): Promise<{ token: string }> {
        return request<{ token: string }>("/settings/secrets-token", { method: "POST" });
    },

    listSecretGroups(): Promise<SecretGroupSummary[]> {
        return request<SecretGroupSummary[]>("/secrets");
    },

    createSecretGroup(group: string, values: Record<string, string>): Promise<{ group: string }> {
        return request<{ group: string }>("/secrets", {
            method: "POST",
            body: JSON.stringify({ group, values }),
        });
    },

    getSecretGroup(group: string): Promise<{ group: string; secrets: Secret[] }> {
        return request<{ group: string; secrets: Secret[] }>(`/secrets/${encodeURIComponent(group)}`);
    },

    putSecrets(group: string, values: Record<string, string>): Promise<{ saved: boolean }> {
        return request<{ saved: boolean }>(`/secrets/${encodeURIComponent(group)}`, {
            method: "PUT",
            body: JSON.stringify(values),
        });
    },

    revealSecret(group: string, key: string): Promise<{ value: string }> {
        return request<{ value: string }>(`/secrets/${encodeURIComponent(group)}/reveal`, {
            method: "POST",
            body: JSON.stringify({ key }),
        });
    },

    deleteSecret(group: string, key: string): Promise<void> {
        return request<void>(`/secrets/${encodeURIComponent(group)}/${encodeURIComponent(key)}`, {
            method: "DELETE",
        });
    },

    deleteSecretGroup(group: string): Promise<void> {
        return request<void>(`/secrets/${encodeURIComponent(group)}`, { method: "DELETE" });
    },

    listKeys(): Promise<Key[]> {
        return request<Key[]>("/keys");
    },

    getKey(keyId: string): Promise<KeyWithAssignments> {
        return request<KeyWithAssignments>(`/keys/${keyId}`);
    },

    uploadKey(label: string, public_key: string, private_key?: string, passphrase?: string): Promise<Key> {
        return request<Key>("/keys", {
            method: "POST",
            body: JSON.stringify({
                label,
                public_key,
                private_key: private_key || undefined,
                passphrase: passphrase || undefined,
            }),
        });
    },

    getPrivateKey(keyId: string): Promise<{ private_key: string }> {
        return request<{ private_key: string }>(`/keys/${keyId}/private-key`);
    },

    deleteKey(keyId: string): Promise<void> {
        return request<void>(`/keys/${keyId}`, { method: "DELETE" });
    },

    assignKey(keyId: string, serverId: string): Promise<Assignment> {
        return request<Assignment>(`/keys/${keyId}/assign`, {
            method: "POST",
            body: JSON.stringify({ server_id: serverId }),
        });
    },

    revokeKey(keyId: string, serverId: string): Promise<void> {
        return request<void>(`/keys/${keyId}/assign/${serverId}`, {
            method: "DELETE",
        });
    },

    connectConsole(body: ConsoleConnectRequest): Promise<ConsoleConnectResponse> {
        return request<ConsoleConnectResponse>("/console/connect", {
            method: "POST",
            body: JSON.stringify(body),
        });
    },

    listSteps(): Promise<WorkflowStep[]> {
        return request<WorkflowStep[]>("/steps");
    },

    createStep(s: Partial<WorkflowStep>): Promise<WorkflowStep> {
        return request<WorkflowStep>("/steps", {
            method: "POST",
            body: JSON.stringify(s),
        });
    },

    updateStep(stepId: string, s: Partial<WorkflowStep>): Promise<WorkflowStep> {
        return request<WorkflowStep>(`/steps/${stepId}`, {
            method: "PUT",
            body: JSON.stringify(s),
        });
    },

    deleteStep(stepId: string): Promise<void> {
        return request<void>(`/steps/${stepId}`, { method: "DELETE" });
    },

    exportStepUrl(stepId: string): string {
        return `/api/steps/${stepId}/export`;
    },

    importStep(doc: unknown): Promise<WorkflowStep> {
        return request<WorkflowStep>("/steps/import", {
            method: "POST",
            body: JSON.stringify(doc),
        });
    },

    parseStep(doc: unknown): Promise<WorkflowStep> {
        return request<WorkflowStep>("/steps/parse", {
            method: "POST",
            body: JSON.stringify(doc),
        });
    },

    seedDefaults(): Promise<{ created: number; updated: number }> {
        return request<{ created: number; updated: number }>("/steps/seed-defaults", {
            method: "POST",
        });
    },

    stepUsage(): Promise<Record<string, number>> {
        return request<Record<string, number>>("/steps/usage");
    },

    listWorkflows(): Promise<Workflow[]> {
        return request<Workflow[]>("/workflows");
    },

    getWorkflow(workflowId: string): Promise<Workflow> {
        return request<Workflow>(`/workflows/${workflowId}`);
    },

    createWorkflow(w: Partial<Workflow>): Promise<Workflow> {
        return request<Workflow>("/workflows", {
            method: "POST",
            body: JSON.stringify(w),
        });
    },

    updateWorkflow(workflowId: string, w: Partial<Workflow>): Promise<Workflow> {
        return request<Workflow>(`/workflows/${workflowId}`, {
            method: "PUT",
            body: JSON.stringify(w),
        });
    },

    deleteWorkflow(workflowId: string): Promise<void> {
        return request<void>(`/workflows/${workflowId}`, { method: "DELETE" });
    },

    runWorkflow(workflowId: string): Promise<{ run_id: string }> {
        return request<{ run_id: string }>(`/workflows/${workflowId}/run`, {
            method: "POST",
        });
    },

    listRuns(workflowId: string): Promise<WorkflowRun[]> {
        return request<WorkflowRun[]>(`/workflows/${workflowId}/runs`);
    },

    getRun(runId: string): Promise<WorkflowRun> {
        return request<WorkflowRun>(`/runs/${runId}`);
    },

    cancelRun(runId: string): Promise<void> {
        return request<void>(`/runs/${runId}/cancel`, { method: "POST" });
    },

    async getServerRunLog(runId: string, serverId: string): Promise<string> {
        const res = await fetch(`/api/runs/${runId}/servers/${serverId}/logs`, {
            credentials: "include",
        });
        if (!res.ok) throw new Error("no logs");
        return res.text();
    },

    serverRunLogStreamUrl(runId: string, serverId: string): string {
        return `/api/runs/${runId}/servers/${serverId}/logs/stream`;
    },
};

export type LicenseState = "valid" | "expired" | "invalid";

export interface LicenseInfo {
    instance_id: string;
    state: LicenseState;
    reason?: string;
    tier?: string;
    expires_at?: string;
    days_remaining?: number;
    limits: { max_servers: number; max_secret_groups: number; max_channels: number };
    features: Record<string, boolean>;
    usage: { servers: number; secret_groups: number; channels: number };
    source: string;
}

// `request` already prefixes /api, so these paths do not repeat it.
export const licence = {
    get(): Promise<LicenseInfo> {
        return request<LicenseInfo>("/license");
    },
    put(blob: string): Promise<{ state: LicenseState; tier: string; expires_at?: string }> {
        return request("/license", { method: "POST", body: JSON.stringify({ blob }) });
    },
};
