'use client';

import type { ProviderModelOption } from '@/lib/provider-model-options';
import { useMemo, useState } from 'react';
import {
  modelOptionsFromIds,
  suggestedModelOptions,
  supportsCustomModelOptions,
} from '@/lib/provider-model-options';
import { useAction, useMutation, useQuery } from 'convex/react';
import { makeFunctionReference } from 'convex/server';
import { KeyRound, Trash2 } from 'lucide-react';
import { toast } from 'sonner';

import type { Id } from '@spoon/backend/convex/_generated/dataModel.js';
import { api } from '@spoon/backend/convex/_generated/api.js';
import {
  Badge,
  Button,
  Card,
  CardContent,
  CardHeader,
  CardTitle,
  Input,
  Label,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
  Switch,
  Textarea,
} from '@spoon/ui';

type Provider =
  | 'openai'
  | 'anthropic'
  | 'google'
  | 'openrouter'
  | 'requesty'
  | 'litellm'
  | 'cloudflare_ai_gateway'
  | 'custom_openai_compatible'
  | 'opencode_openai_login';
type AuthType =
  | 'api_key'
  | 'opencode_auth_json'
  | 'anthropic_oauth_json'
  | 'none';
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';

const saveProfileRef = makeFunctionReference<
  'action',
  {
    profileId?: Id<'aiProviderProfiles'>;
    name: string;
    provider: Provider;
    authType: AuthType;
    secret?: string;
    baseUrl?: string;
    defaultModel: string;
    modelOptions?: string[];
    reasoningEffort: ReasoningEffort;
    enabled: boolean;
  },
  Id<'aiProviderProfiles'>
>('aiProviderProfilesNode:save');

const setDefaultProfileRef = makeFunctionReference<
  'mutation',
  { profileId: Id<'aiProviderProfiles'> },
  { success: true }
>('aiProviderProfiles:setDefault');

const providerOptions: {
  key: string;
  value: Provider;
  label: string;
  authType: AuthType;
}[] = [
  {
    key: 'openai',
    value: 'openai',
    label: 'OpenAI API key',
    authType: 'api_key',
  },
  {
    key: 'anthropic',
    value: 'anthropic',
    label: 'Anthropic API key',
    authType: 'api_key',
  },
  {
    key: 'google',
    value: 'google',
    label: 'Google Gemini API key',
    authType: 'api_key',
  },
  {
    key: 'openrouter',
    value: 'openrouter',
    label: 'OpenRouter',
    authType: 'api_key',
  },
  {
    key: 'requesty',
    value: 'requesty',
    label: 'Requesty',
    authType: 'api_key',
  },
  { key: 'litellm', value: 'litellm', label: 'LiteLLM', authType: 'api_key' },
  {
    key: 'cloudflare_ai_gateway',
    value: 'cloudflare_ai_gateway',
    label: 'Cloudflare AI Gateway',
    authType: 'api_key',
  },
  {
    key: 'custom_openai_compatible',
    value: 'custom_openai_compatible',
    label: 'Custom OpenAI-compatible',
    authType: 'api_key',
  },
  {
    key: 'opencode_openai_login',
    value: 'opencode_openai_login',
    label: 'Codex ChatGPT login',
    authType: 'opencode_auth_json',
  },
  {
    key: 'anthropic_oauth_json',
    value: 'anthropic',
    label: 'Anthropic OAuth (paste credentials)',
    authType: 'anthropic_oauth_json',
  },
];

const defaultProviderOption: (typeof providerOptions)[number] = {
  key: 'openai',
  value: 'openai',
  label: 'OpenAI API key',
  authType: 'api_key',
};

const reasoningOptions: ReasoningEffort[] = [
  'none',
  'minimal',
  'low',
  'medium',
  'high',
  'xhigh',
];

export const AiProviderProfilesPanel = () => {
  const profiles = useQuery(api.aiProviderProfiles.listMine, {}) ?? [];
  const saveProfile = useAction(saveProfileRef);
  const setDefaultProfile = useMutation(setDefaultProfileRef);
  const removeProfile = useMutation(api.aiProviderProfiles.remove);
  const [profileId, setProfileId] = useState<Id<'aiProviderProfiles'>>();
  const [name, setName] = useState('OpenAI');
  const [providerKey, setProviderKey] = useState('openai');
  const selectedProvider = useMemo(
    () =>
      providerOptions.find((option) => option.key === providerKey) ??
      defaultProviderOption,
    [providerKey],
  );
  const provider = selectedProvider.value;
  const [secret, setSecret] = useState('');
  const [baseUrl, setBaseUrl] = useState('');
  const [defaultModelValue, setDefaultModelValue] = useState(
    suggestedModelOptions('openai')[0]?.id ?? '',
  );
  const [modelOptions, setModelOptions] = useState<ProviderModelOption[]>(
    suggestedModelOptions('openai'),
  );
  const [customModelId, setCustomModelId] = useState('');
  const [reasoningEffort, setReasoningEffort] =
    useState<ReasoningEffort>('medium');
  const [enabled, setEnabled] = useState(true);
  const [saving, setSaving] = useState(false);

  const resetModelOptions = (nextProvider: Provider) => {
    const options = suggestedModelOptions(nextProvider);
    setModelOptions(options);
    setDefaultModelValue(options[0]?.id ?? '');
    setCustomModelId('');
  };

  const reset = () => {
    setProfileId(undefined);
    setProviderKey('openai');
    setSecret('');
    setBaseUrl('');
    setDefaultModelValue('');
    setModelOptions(suggestedModelOptions('openai'));
    setCustomModelId('');
    setReasoningEffort('medium');
    setEnabled(true);
    setName('OpenAI');
  };

  const edit = (profile: (typeof profiles)[number]) => {
    setProfileId(profile._id);
    setName(profile.name);
    setProviderKey(
      providerOptions.find(
        (option) =>
          option.value === profile.provider &&
          option.authType === profile.authType,
      )?.key ??
        providerOptions.find((option) => option.value === profile.provider)
          ?.key ??
        'openai',
    );
    setSecret('');
    setBaseUrl(profile.baseUrl ?? '');
    setDefaultModelValue(profile.defaultModel);
    setModelOptions(
      modelOptionsFromIds(
        profile.modelOptions?.length
          ? profile.modelOptions
          : [profile.defaultModel],
      ),
    );
    setCustomModelId('');
    setReasoningEffort(profile.reasoningEffort as ReasoningEffort);
    setEnabled(profile.enabled);
  };

  const save = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSaving(true);
    try {
      await saveProfile({
        profileId,
        name,
        provider,
        authType: selectedProvider.authType,
        secret: secret.trim() ? secret : undefined,
        baseUrl: baseUrl.trim() || undefined,
        defaultModel: defaultModelValue,
        modelOptions: modelOptions.map((model) => model.id),
        reasoningEffort,
        enabled,
      });
      toast.success('AI provider saved.');
      reset();
    } catch (error) {
      console.error(error);
      toast.error('Could not save AI provider.');
    } finally {
      setSaving(false);
    }
  };

  const selectedProfile = profileId
    ? profiles.find((profile) => profile._id === profileId)
    : undefined;
  const hasCredential =
    selectedProvider.authType === 'none' ||
    Boolean(secret.trim()) ||
    Boolean(selectedProfile?.configured);
  const canSelectModel = hasCredential && modelOptions.length > 0;
  const configuredProfiles = profiles.filter(
    (profile) => profile.enabled && profile.configured,
  );
  const defaultProfile = configuredProfiles.find(
    (profile) => profile.isDefault,
  );

  return (
    <div className='grid gap-4 xl:grid-cols-[1fr_0.9fr]'>
      <Card className='shadow-none'>
        <CardHeader>
          <CardTitle className='flex items-center gap-2 text-base'>
            <KeyRound className='size-4' />
            Provider profiles
          </CardTitle>
        </CardHeader>
        <CardContent className='space-y-3'>
          {configuredProfiles.length > 1 ? (
            <div className='grid gap-2 rounded-md border p-3'>
              <Label>Default provider</Label>
              <Select
                value={defaultProfile?._id ?? ''}
                onValueChange={async (value) => {
                  await setDefaultProfile({
                    profileId: value as Id<'aiProviderProfiles'>,
                  });
                  toast.success('Default AI provider updated.');
                }}
              >
                <SelectTrigger>
                  <SelectValue placeholder='Choose default provider' />
                </SelectTrigger>
                <SelectContent>
                  {configuredProfiles.map((profile) => (
                    <SelectItem key={profile._id} value={profile._id}>
                      {profile.name} · {profile.defaultModel}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <p className='text-muted-foreground text-xs'>
                Spoons using account default will use this provider.
              </p>
            </div>
          ) : null}
          {profiles.length ? (
            profiles.map((profile) => (
              <div
                key={profile._id}
                className='border-border flex items-center justify-between gap-3 rounded-md border p-3'
              >
                <button
                  type='button'
                  className='min-w-0 text-left'
                  onClick={() => edit(profile)}
                >
                  <p className='truncate text-sm font-medium'>{profile.name}</p>
                  <p className='text-muted-foreground text-xs'>
                    {profile.provider.replaceAll('_', ' ')} ·{' '}
                    {profile.secretPreview ?? 'not configured'} ·{' '}
                    {profile.defaultModel}
                    {profile.isDefault ? ' · default' : ''}
                  </p>
                </button>
                <Button
                  type='button'
                  variant='ghost'
                  size='icon'
                  aria-label='Remove provider'
                  onClick={async () => {
                    await removeProfile({ profileId: profile._id });
                    toast.success('AI provider removed.');
                  }}
                >
                  <Trash2 className='size-4' />
                </Button>
              </div>
            ))
          ) : (
            <p className='text-muted-foreground text-sm'>
              Add API-key providers for OpenCode, or store a Codex ChatGPT login
              profile for runs that should use your Codex plan.
            </p>
          )}
        </CardContent>
      </Card>
      <Card className='shadow-none'>
        <CardHeader>
          <CardTitle className='text-base'>
            {profileId ? 'Edit provider' : 'Add provider'}
          </CardTitle>
        </CardHeader>
        <CardContent>
          <form onSubmit={save} className='space-y-4'>
            <div className='grid gap-2'>
              <Label>Name</Label>
              <Input
                value={name}
                onChange={(event) => setName(event.target.value)}
              />
            </div>
            <div className='grid gap-2'>
              <Label>Provider</Label>
              <Select
                value={providerKey}
                onValueChange={(value) => {
                  const nextOption =
                    providerOptions.find((option) => option.key === value) ??
                    defaultProviderOption;
                  setProviderKey(nextOption.key);
                  resetModelOptions(nextOption.value);
                  setName(nextOption.label.replace(' API key', ''));
                }}
              >
                <SelectTrigger>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {providerOptions.map((option) => (
                    <SelectItem key={option.key} value={option.key}>
                      {option.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div className='grid gap-2'>
              <Label>
                {selectedProvider.authType === 'opencode_auth_json'
                  ? 'Codex auth JSON'
                  : selectedProvider.authType === 'anthropic_oauth_json'
                    ? 'Anthropic credentials JSON'
                    : 'API key'}
              </Label>
              {selectedProvider.authType === 'opencode_auth_json' ? (
                <>
                  <Textarea
                    value={secret}
                    placeholder='Paste the full auth.json contents.'
                    onChange={(event) => setSecret(event.target.value)}
                  />
                  <p className='text-muted-foreground text-xs'>
                    Copy your Codex auth file from{' '}
                    <code className='bg-muted rounded px-1 py-0.5'>
                      ~/.codex/auth.json
                    </code>
                    . Spoon writes it into the isolated job workspace as
                    Codex&apos;s auth cache. It is stored encrypted and should
                    be treated like a password.
                  </p>
                </>
              ) : selectedProvider.authType === 'anthropic_oauth_json' ? (
                <>
                  <Textarea
                    value={secret}
                    placeholder='Paste the full .credentials.json contents.'
                    onChange={(event) => setSecret(event.target.value)}
                  />
                  <p className='text-muted-foreground text-xs'>
                    Copy your Claude credentials file from{' '}
                    <code className='bg-muted rounded px-1 py-0.5'>
                      ~/.claude/.credentials.json
                    </code>
                    . Spoon writes it into the isolated job workspace as
                    Claude&apos;s auth cache. It is stored encrypted and should
                    be treated like a password.
                  </p>
                </>
              ) : (
                <Input
                  type='password'
                  value={secret}
                  placeholder={
                    profileId ? 'Leave blank to keep current secret' : 'sk-...'
                  }
                  onChange={(event) => setSecret(event.target.value)}
                />
              )}
            </div>
            <div className='grid gap-2'>
              <Label>Base URL</Label>
              <Input
                value={baseUrl}
                placeholder='Optional for LiteLLM, Requesty, custom providers'
                onChange={(event) => setBaseUrl(event.target.value)}
              />
            </div>
            <div className='grid gap-2 md:grid-cols-2'>
              <div className='grid gap-2'>
                <Label>Default model</Label>
                <Select
                  value={defaultModelValue}
                  onValueChange={setDefaultModelValue}
                  disabled={!canSelectModel}
                >
                  <SelectTrigger>
                    <SelectValue
                      placeholder={
                        hasCredential
                          ? 'Choose a model'
                          : 'Add credentials first'
                      }
                    />
                  </SelectTrigger>
                  <SelectContent>
                    {modelOptions.map((model) => (
                      <SelectItem key={model.id} value={model.id}>
                        {model.label}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                <p className='text-muted-foreground text-xs'>
                  Saved model options are used by Spoons. Add custom model IDs
                  for compatible provider gateways.
                </p>
                <div className='rounded-md border p-2'>
                  <p className='text-muted-foreground mb-2 text-xs'>
                    Available model options
                  </p>
                  <div className='flex flex-wrap gap-2'>
                    {modelOptions.map((model) => (
                      <Badge key={model.id} variant='outline'>
                        {model.id}
                      </Badge>
                    ))}
                  </div>
                </div>
                {supportsCustomModelOptions(provider) ? (
                  <div className='flex gap-2'>
                    <Input
                      value={customModelId}
                      placeholder='provider/model-id'
                      onChange={(event) => setCustomModelId(event.target.value)}
                    />
                    <Button
                      type='button'
                      variant='outline'
                      onClick={() => {
                        const id = customModelId.trim();
                        if (!id) return;
                        setModelOptions((current) =>
                          current.some((model) => model.id === id)
                            ? current
                            : [...current, ...modelOptionsFromIds([id])],
                        );
                        setDefaultModelValue((current) => current || id);
                        setCustomModelId('');
                      }}
                    >
                      Add
                    </Button>
                  </div>
                ) : null}
              </div>
              <div className='grid gap-2'>
                <Label>Thinking</Label>
                <Select
                  value={reasoningEffort}
                  onValueChange={(value) =>
                    setReasoningEffort(value as ReasoningEffort)
                  }
                >
                  <SelectTrigger>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {reasoningOptions.map((option) => (
                      <SelectItem key={option} value={option}>
                        {option}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>
            </div>
            <div className='flex items-center justify-between gap-4 rounded-md border p-3'>
              <div>
                <Label>Enabled</Label>
                <p className='text-muted-foreground text-xs'>
                  Disabled profiles cannot be selected for new jobs.
                </p>
              </div>
              <Switch checked={enabled} onCheckedChange={setEnabled} />
            </div>
            <div className='flex flex-wrap gap-2'>
              <Button
                type='submit'
                disabled={saving || !hasCredential || !defaultModelValue}
              >
                {saving ? 'Saving...' : 'Save provider'}
              </Button>
              {profileId ? (
                <Button type='button' variant='outline' onClick={reset}>
                  New provider
                </Button>
              ) : null}
            </div>
          </form>
        </CardContent>
      </Card>
    </div>
  );
};
