import React, { useEffect, useRef, useState } from 'react';
import { FaUpload } from 'react-icons/fa';

import type {
  BrandResponse,
  VisibilitySettingsResponse,
} from '../../service/visibility/visibility.interface';
import { DOCUMENT_ACCEPT, extractDocumentText } from '../../utils/documentText';
import {
  createBrand,
  patchBrand,
  updateSettings,
} from '../../service/visibility/visibility.service';
import { setSetupProgressStep } from '../../service/setup-progress/setup-progress.service';
import { WORDPRESS_STEP_KEYS } from '../../service/setup-progress/setup-progress.constants';
import { addBulkTags, containsBulkSeparator } from '../../utils/bulkTags';
import { COUNTRY_OPTIONS, LANGUAGE_OPTIONS } from '../../data/locales';
import SearchableSelect from '../widgets/SearchableSelect';

import Modal from './Modal';

const MAX_TAGS = 12;

/** Hard cap on the knowledge base text - matches the brand API schema. */
const MAX_KNOWLEDGE_BASE_CHARS = 10_000;

interface SettingsDrawerProps {
  open: boolean;
  onClose: () => void;
  brand: BrandResponse | null;
  settings: VisibilitySettingsResponse | null;
  clientId: string;
  token: string;
  fallbackDomain?: string;
  /**
   * Storefront language detected from the host platform (the WordPress
   * site locale). Preferred over the merchant-wide
   * ``preferred_language`` because that one tracks the Recomaze admin
   * UI, not the storefront the shoppers and AI assistants actually see.
   */
  fallbackLanguage?: string;
  onBrandSaved: (brand: BrandResponse) => void;
  onSettingsSaved: (settings: VisibilitySettingsResponse) => void;
}

const TagChip = ({
  value,
  onRemove,
}: {
  value: string;
  onRemove: () => void;
}): JSX.Element => (
  <span className="inline-flex items-center gap-1.5 rounded-full bg-gray-100 border border-gray-200 px-2 py-0.5 text-xs text-gray-900">
    {value}
    <button
      type="button"
      onClick={onRemove}
      aria-label={`Remove ${value}`}
      className="text-gray-500 hover:text-gray-700"
    >
      ×
    </button>
  </span>
);

/**
 * Side panel for brand + merchant-wide visibility settings. Saves touch
 * two endpoints:
 *   - Brand-scope → ``PATCH /brands/{id}`` (or ``POST /brands`` when missing)
 *   - Merchant-scope → ``PUT /settings``
 */
const SettingsDrawer = ({
  open,
  onClose,
  brand,
  settings,
  clientId,
  token,
  fallbackDomain,
  fallbackLanguage,
  onBrandSaved,
  onSettingsSaved,
}: SettingsDrawerProps): JSX.Element => {
  const [language, setLanguage] = useState('en');
  const [country, setCountry] = useState('');
  const [domain, setDomain] = useState('');
  const [brandName, setBrandName] = useState('');
  const [niche, setNiche] = useState('');
  const [businessType, setBusinessType] = useState('');
  const [keywords, setKeywords] = useState<string[]>([]);
  const [keywordDraft, setKeywordDraft] = useState('');
  const [keywordStatus, setKeywordStatus] = useState<string | null>(null);
  const [differentiators, setDifferentiators] = useState<string[]>([]);
  const [differentiatorDraft, setDifferentiatorDraft] = useState('');
  const [differentiatorStatus, setDifferentiatorStatus] = useState<
    string | null
  >(null);
  const [knowledgeBase, setKnowledgeBase] = useState('');
  const [knowledgeBaseFileError, setKnowledgeBaseFileError] = useState<
    string | null
  >(null);
  const knowledgeFileInputRef = useRef<HTMLInputElement>(null);
  const [weeklyEmail, setWeeklyEmail] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (open) {
      // Order: explicit brand language → detected storefront (WP) locale
      // → merchant-wide preferred_language → English. Storefront wins
      // over preferred_language so a Romanian WordPress store doesn't
      // get scanned in English just because the admin UI is in English.
      setLanguage(
        brand?.language ||
          fallbackLanguage ||
          settings?.preferred_language ||
          'en'
      );
      setCountry(brand?.country || settings?.default_country || '');
      setDomain(brand?.domain || fallbackDomain || '');
      setBrandName(brand?.brand_name || '');
      setNiche(brand?.niche || '');
      setBusinessType(brand?.business_type || '');
      setKeywords(
        Array.isArray(brand?.keywords) ? [...(brand?.keywords ?? [])] : []
      );
      setKeywordDraft('');
      setKeywordStatus(null);
      setDifferentiators(
        Array.isArray(brand?.differentiators)
          ? [...(brand?.differentiators ?? [])]
          : []
      );
      setDifferentiatorDraft('');
      setDifferentiatorStatus(null);
      setKnowledgeBase(brand?.knowledge_base || '');
      setKnowledgeBaseFileError(null);
      setWeeklyEmail(true);
      setError(null);
    }
  }, [open, brand, settings, fallbackDomain, fallbackLanguage]);

  /**
   * Read an attached .txt/.csv/.docx into the knowledge base textarea,
   * truncated to the field's character budget so the merchant can review and
   * edit it before saving.
   *
   * @param file {File | null} The selected file, or null when cleared.
   * @returns {Promise<void>} Resolves once the file text has been loaded.
   */
  const handleKnowledgeFile = async (file: File | null): Promise<void> => {
    setKnowledgeBaseFileError(null);
    if (!file) return;
    const result = await extractDocumentText(file, MAX_KNOWLEDGE_BASE_CHARS);
    if (result.error) {
      setKnowledgeBaseFileError(result.error);
      return;
    }
    setKnowledgeBase(result.text ?? '');
  };

  /**
   * Strip protocol, ``www.``, paths and query strings, and validate the
   * result looks like a real hostname (at least one dot, no whitespace,
   * all-ASCII). Backend also runs a reachability probe on its end, but
   * we catch the obvious mistakes (``t``, ``localhost``, spaces) client
   * side so the merchant doesn't hit a round-trip for a typo.
   */
  const normaliseDomain = (raw: string): string => {
    let d = raw.trim().toLowerCase();
    d = d.replace(/^https?:\/\//, '');
    d = d.replace(/^www\./, '');
    d = d.split('/')[0].split('?')[0].split('#')[0];

    return d;
  };

  const isValidDomain = (d: string): boolean => {
    if (!d || d.length < 4) return false;
    if (/\s/.test(d)) return false;
    if (!d.includes('.')) return false;

    return /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(d);
  };

  /**
   * Adds one or many tags from ``value`` (typed draft, Enter, Add click,
   * or pasted blob). Comma / newline / tab separators in ``value`` are
   * treated as a bulk paste and split into individual tags. Trimming,
   * empty-drop, case-insensitive dedupe, and the {@link MAX_TAGS} cap
   * are all enforced by {@link addBulkTags}.
   */
  const commitTags = (
    value: string,
    current: string[],
    setCurrent: (next: string[]) => void,
    setDraft: (next: string) => void,
    setStatus: (next: string | null) => void
  ) => {
    const result = addBulkTags(value, current, MAX_TAGS);
    if (result.added.length > 0) {
      setCurrent(result.next);
    }
    setDraft('');
    if (result.total > 0 && result.added.length < result.total) {
      setStatus(`${result.added.length} of ${result.total} added`);
    } else {
      setStatus(null);
    }
  };

  /**
   * Paste handler shared by both pill inputs. Single-value pastes (no
   * separators) fall through to the default browser behavior; multi-value
   * pastes are intercepted and routed through {@link commitTags}.
   */
  const handleTagPaste = (
    event: React.ClipboardEvent<HTMLInputElement>,
    current: string[],
    setCurrent: (next: string[]) => void,
    setDraft: (next: string) => void,
    setStatus: (next: string | null) => void
  ) => {
    const text = event.clipboardData.getData('text');
    if (!containsBulkSeparator(text)) return;
    event.preventDefault();
    commitTags(text, current, setCurrent, setDraft, setStatus);
  };

  const handleSave = async () => {
    if (!settings) return;
    setSaving(true);
    setError(null);

    // When creating a brand, validate the domain client-side so we don't
    // hit the backend just to learn the domain is malformed (the backend
    // also runs a reachability probe; that's a separate check).
    let normalisedDomain = '';
    if (!brand) {
      normalisedDomain = normaliseDomain(domain);
      if (!isValidDomain(normalisedDomain)) {
        setError(
          'Enter a valid domain (e.g. mystore.com). Protocols and paths are stripped automatically.'
        );
        setSaving(false);
        return;
      }
    }

    try {
      const brandPromise = brand
        ? patchBrand(clientId, token, brand.brand_id, {
            brand_name: brandName.trim() || undefined,
            language,
            country: country || undefined,
            niche: niche.trim() || undefined,
            business_type: businessType.trim() || undefined,
            keywords,
            differentiators,
            knowledge_base: knowledgeBase.trim(),
          })
        : createBrand(clientId, token, {
            domain: normalisedDomain,
            brand_name: brandName.trim() || undefined,
            language,
            country: country || undefined,
            niche: niche.trim() || undefined,
            business_type: businessType.trim() || undefined,
            keywords,
            differentiators,
            knowledge_base: knowledgeBase.trim() || undefined,
          });
      const [savedBrand, savedSettings] = await Promise.all([
        brandPromise,
        updateSettings(clientId, token, {
          preferred_language: language,
          default_country: country || undefined,
        }),
      ]);
      onBrandSaved(savedBrand);
      onSettingsSaved(savedSettings);
      setSetupProgressStep(WORDPRESS_STEP_KEYS.RUN_AI_VISIBILITY).catch(
        () => {}
      );
      onClose();
    } catch (err: any) {
      // Surface the backend's ``detail`` field when available (e.g.
      // "Domain is invalid or not reachable") - it's far more actionable
      // than a generic "Failed to save settings".
      const backendDetail =
        err?.response?.data?.detail ||
        err?.response?.data?.message ||
        (typeof err?.response?.data === 'string' ? err.response.data : null);
      setError(
        backendDetail ||
          (err instanceof Error ? err.message : 'Failed to save settings.')
      );
    } finally {
      setSaving(false);
    }
  };

  const addButtonClass = (disabled: boolean): string =>
    `rounded-md border border-gray-300 px-3 py-1.5 text-sm font-medium ${
      disabled
        ? 'bg-gray-50 text-gray-400 cursor-not-allowed'
        : 'bg-black text-white hover:bg-gray-800'
    }`;

  return (
    <Modal
      open={open}
      onClose={onClose}
      title="Visibility settings"
      size="lg"
      footer={
        <>
          <button
            type="button"
            onClick={onClose}
            disabled={saving}
            className="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
          >
            Cancel
          </button>
          <button
            type="button"
            onClick={handleSave}
            disabled={saving || !settings}
            className="rounded-md bg-black px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-60"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>
        </>
      }
    >
      <div className="flex flex-col gap-6">
        {error && (
          <div className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700">
            {error}
          </div>
        )}

        <div className="flex flex-col gap-4">
          <div className="flex flex-col gap-1">
            <label
              htmlFor="brand-domain"
              className="text-sm font-medium text-gray-900"
            >
              Domain <span className="text-red-600">*</span>
            </label>
            <input
              id="brand-domain"
              type="text"
              value={domain}
              onChange={e => setDomain(e.target.value)}
              disabled={!!brand}
              placeholder="mystore.com"
              autoComplete="off"
              className="rounded-md border border-gray-300 px-3 py-1.5 text-sm disabled:bg-gray-50 disabled:text-gray-500"
            />
            <p className="text-xs text-gray-500">
              The canonical domain of your storefront (e.g. mystore.com). Must
              be a real, reachable domain - the scanner uses it to ground AI
              search queries. Cannot be changed after the brand is created.
            </p>
          </div>
          <div className="flex flex-col gap-1">
            <label
              htmlFor="brand-name"
              className="text-sm font-medium text-gray-900"
            >
              Brand name
            </label>
            <input
              id="brand-name"
              type="text"
              value={brandName}
              onChange={e => setBrandName(e.target.value)}
              placeholder="Optional - defaults to the domain"
              autoComplete="off"
              className="rounded-md border border-gray-300 px-3 py-1.5 text-sm"
            />
          </div>
          <div className="flex flex-col gap-1">
            <label
              htmlFor="brand-language"
              className="text-sm font-medium text-gray-900"
            >
              Brand language
            </label>
            <SearchableSelect
              id="brand-language"
              value={language}
              onChange={setLanguage}
              options={LANGUAGE_OPTIONS}
              placeholder="Select a language..."
              searchPlaceholder="Search a language..."
            />
            <p className="text-xs text-gray-500">
              Drives the language of problem reasons, article content, and
              suggested topics.
            </p>
          </div>
          <div className="flex flex-col gap-1">
            <label
              htmlFor="brand-country"
              className="text-sm font-medium text-gray-900"
            >
              Country
            </label>
            <SearchableSelect
              id="brand-country"
              value={country}
              onChange={setCountry}
              options={COUNTRY_OPTIONS}
              placeholder="Select a country..."
              searchPlaceholder="Search a country..."
            />
            <p className="text-xs text-gray-500">
              Optional geo hint the scanner uses when grounding searches.
            </p>
          </div>
        </div>

        {/* How AI sees your brand */}
        <div className="flex flex-col gap-3 border-t border-gray-200 pt-4">
          <div className="flex flex-col gap-1">
            <h3 className="text-base font-semibold text-gray-900">
              How AI sees your brand
            </h3>
            <p className="text-xs text-gray-500">
              These were detected from your domain. Override only if Gemini got
              something wrong - bad inputs here will push the scanner toward the
              wrong prompts.
            </p>
          </div>
          <div className="flex flex-col gap-1">
            <label
              htmlFor="brand-niche"
              className="text-sm font-medium text-gray-900"
            >
              Niche
            </label>
            <input
              id="brand-niche"
              type="text"
              value={niche}
              onChange={e => setNiche(e.target.value)}
              placeholder="e.g. premium skincare, B2B SaaS analytics, eco-friendly home goods"
              autoComplete="off"
              className="rounded-md border border-gray-300 px-3 py-1.5 text-sm"
            />
            <p className="text-xs text-gray-500">
              One sentence fragment is enough. Leave empty to let Gemini decide.
            </p>
          </div>
          <div className="flex flex-col gap-1">
            <label
              htmlFor="brand-business-type"
              className="text-sm font-medium text-gray-900"
            >
              Business type
            </label>
            <input
              id="brand-business-type"
              type="text"
              value={businessType}
              onChange={e => setBusinessType(e.target.value)}
              placeholder="e.g. ecommerce, saas, agency, marketplace"
              autoComplete="off"
              className="rounded-md border border-gray-300 px-3 py-1.5 text-sm"
            />
          </div>

          {/* Knowledge base */}
          <div className="flex flex-col gap-2 rounded-lg border border-gray-200 bg-gray-50/60 p-3">
            <div className="flex flex-wrap items-center justify-between gap-2">
              <div className="flex items-center gap-2">
                <label
                  htmlFor="brand-knowledge-base"
                  className="text-sm font-medium text-gray-900"
                >
                  Knowledge base
                </label>
                <span className="animate-pulse rounded-full bg-gradient-to-r from-emerald-400 to-green-500 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-white shadow-[0_0_10px_rgba(16,185,129,0.8)]">
                  New
                </span>
                <span className="text-xs text-gray-500">Optional</span>
              </div>
              <button
                type="button"
                onClick={() => knowledgeFileInputRef.current?.click()}
                className="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-50"
              >
                <FaUpload className="h-3 w-3" />
                Upload document
              </button>
              <input
                ref={knowledgeFileInputRef}
                type="file"
                accept={DOCUMENT_ACCEPT}
                className="hidden"
                onChange={e => {
                  void handleKnowledgeFile(e.target.files?.[0] ?? null);
                  e.target.value = '';
                }}
              />
            </div>
            <p className="text-xs text-gray-500">
              Tell us about your business in your own words - what you sell, who
              you sell to, your positioning and what makes you different. We
              feed it to the AI so the tracked prompts and the keywords we
              monitor reflect your real market instead of generic category
              guesses. Paste text or upload a .txt, .csv or .docx.
            </p>
            <p className="text-xs font-medium text-red-600">
              When filled, the knowledge base takes priority: your keywords and
              tracked prompts are rebuilt from it on the next Force scan. Your
              products are still used to write the articles, but the angles are
              framed around your knowledge base.
            </p>
            <textarea
              id="brand-knowledge-base"
              value={knowledgeBase}
              onChange={e =>
                setKnowledgeBase(
                  e.target.value.slice(0, MAX_KNOWLEDGE_BASE_CHARS)
                )
              }
              placeholder="e.g. We manufacture premium, food-safe cake boxes for bakeries and patisseries across Romania. Our edge is custom logo printing and fast nationwide delivery..."
              rows={5}
              maxLength={MAX_KNOWLEDGE_BASE_CHARS}
              className="rounded-md border border-gray-300 px-3 py-1.5 text-sm"
            />
            <div className="flex items-center justify-between text-xs text-gray-500">
              <span>
                {knowledgeBaseFileError ? (
                  <span className="text-red-600">{knowledgeBaseFileError}</span>
                ) : (
                  '.txt, .csv or .docx, up to 5 MB'
                )}
              </span>
              <span>{`${knowledgeBase.length} / ${MAX_KNOWLEDGE_BASE_CHARS}`}</span>
            </div>
          </div>

          {/* Keywords */}
          <div className="flex flex-col gap-2">
            <span className="text-sm font-medium text-gray-900">Keywords</span>
            <p className="text-xs text-gray-500">
              Seed keywords Gemini uses to expand scan prompts. Up to {MAX_TAGS}
              . Press Enter or paste a comma- or newline-separated list to add
              several at once.
            </p>
            <div className="flex items-stretch gap-2">
              <input
                type="text"
                value={keywordDraft}
                onChange={e => {
                  setKeywordDraft(e.target.value);
                  if (keywordStatus) setKeywordStatus(null);
                }}
                onKeyDown={e => {
                  if (e.key === 'Enter') {
                    e.preventDefault();
                    commitTags(
                      keywordDraft,
                      keywords,
                      setKeywords,
                      setKeywordDraft,
                      setKeywordStatus
                    );
                  }
                }}
                onPaste={e =>
                  handleTagPaste(
                    e,
                    keywords,
                    setKeywords,
                    setKeywordDraft,
                    setKeywordStatus
                  )
                }
                placeholder="e.g. sustainable office chairs"
                autoComplete="off"
                className="flex-1 rounded-md border border-gray-300 px-3 py-1.5 text-sm"
              />
              <button
                type="button"
                disabled={!keywordDraft.trim() || keywords.length >= MAX_TAGS}
                onClick={() =>
                  commitTags(
                    keywordDraft,
                    keywords,
                    setKeywords,
                    setKeywordDraft,
                    setKeywordStatus
                  )
                }
                className={addButtonClass(
                  !keywordDraft.trim() || keywords.length >= MAX_TAGS
                )}
              >
                Add
              </button>
            </div>
            {keywordStatus && (
              <p className="text-xs text-gray-500">{keywordStatus}</p>
            )}
            {keywords.length > 0 && (
              <div className="flex flex-wrap gap-2">
                {keywords.map(kw => (
                  <TagChip
                    key={kw}
                    value={kw}
                    onRemove={() => {
                      setKeywords(keywords.filter(k => k !== kw));
                      setKeywordStatus(null);
                    }}
                  />
                ))}
              </div>
            )}
          </div>

          {/* Differentiators */}
          <div className="flex flex-col gap-2">
            <span className="text-sm font-medium text-gray-900">
              Differentiators
            </span>
            <p className="text-xs text-gray-500">
              What sets your brand apart - AI uses these to craft distinctive
              article angles. Up to {MAX_TAGS}. Paste a comma- or newline-
              separated list to add several at once.
            </p>
            <div className="flex items-stretch gap-2">
              <input
                type="text"
                value={differentiatorDraft}
                onChange={e => {
                  setDifferentiatorDraft(e.target.value);
                  if (differentiatorStatus) setDifferentiatorStatus(null);
                }}
                onKeyDown={e => {
                  if (e.key === 'Enter') {
                    e.preventDefault();
                    commitTags(
                      differentiatorDraft,
                      differentiators,
                      setDifferentiators,
                      setDifferentiatorDraft,
                      setDifferentiatorStatus
                    );
                  }
                }}
                onPaste={e =>
                  handleTagPaste(
                    e,
                    differentiators,
                    setDifferentiators,
                    setDifferentiatorDraft,
                    setDifferentiatorStatus
                  )
                }
                placeholder="e.g. lifetime warranty, carbon-neutral shipping"
                autoComplete="off"
                className="flex-1 rounded-md border border-gray-300 px-3 py-1.5 text-sm"
              />
              <button
                type="button"
                disabled={
                  !differentiatorDraft.trim() ||
                  differentiators.length >= MAX_TAGS
                }
                onClick={() =>
                  commitTags(
                    differentiatorDraft,
                    differentiators,
                    setDifferentiators,
                    setDifferentiatorDraft,
                    setDifferentiatorStatus
                  )
                }
                className={addButtonClass(
                  !differentiatorDraft.trim() ||
                    differentiators.length >= MAX_TAGS
                )}
              >
                Add
              </button>
            </div>
            {differentiatorStatus && (
              <p className="text-xs text-gray-500">{differentiatorStatus}</p>
            )}
            {differentiators.length > 0 && (
              <div className="flex flex-wrap gap-2">
                {differentiators.map(diff => (
                  <TagChip
                    key={diff}
                    value={diff}
                    onRemove={() => {
                      setDifferentiators(
                        differentiators.filter(d => d !== diff)
                      );
                      setDifferentiatorStatus(null);
                    }}
                  />
                ))}
              </div>
            )}
          </div>
        </div>

        {/* Preferences */}
        <div className="flex flex-col gap-2 border-t border-gray-200 pt-4">
          <label className="flex items-start gap-2 text-sm text-gray-900">
            <input
              type="checkbox"
              checked={weeklyEmail}
              onChange={e => setWeeklyEmail(e.target.checked)}
              className="mt-0.5"
            />
            <span className="flex flex-col gap-0.5">
              <span>Send the weekly visibility email</span>
              <span className="text-xs text-gray-500">
                You&apos;ll still receive scan failure alerts even when this is
                off.
              </span>
            </span>
          </label>
        </div>
      </div>
    </Modal>
  );
};

export default SettingsDrawer;
