import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import type { ReactNode } from 'react';

export type Role = 'USER' | 'ADMIN';
export type AuthProviderName = 'LOCAL' | 'GOOGLE' | 'FACEBOOK';
export type AccountType = 'PERSONAL' | 'COMPANY';
export type ContactPreference = 'EMAIL' | 'PHONE' | 'EMAIL_AND_PHONE';
export type PreferredLanguage = 'PL' | 'EN' | 'UK' | 'DE';

export type AuthUser = {
  id: number;
  email: string;
  fullName: string;
  role: Role;
  provider: AuthProviderName;
  accountType: AccountType;
  phone: string | null;
  address: string | null;
  contactPreference: ContactPreference;
  preferredLanguage: PreferredLanguage;
  nip: string | null;
  birthDate: string | null;
  verified: boolean;
  blocked: boolean;
  createdAt: string;
};

type AuthResponse = { token: string; user: AuthUser };

export type RegisterDetails = {
  accountType: AccountType;
  phone?: string;
  nip?: string;
};

type AuthContextValue = {
  user: AuthUser | null;
  token: string | null;
  loading: boolean;
  login: (email: string, password: string) => Promise<AuthUser>;
  register: (email: string, password: string, fullName: string, details?: RegisterDetails) => Promise<AuthUser>;
  socialLogin: (provider: Exclude<AuthProviderName, 'LOCAL'>) => Promise<AuthUser>;
  updateProfile: (fullName: string, phone?: string, birthDate?: string, address?: string, contactPreference?: ContactPreference, preferredLanguage?: PreferredLanguage) => Promise<AuthUser>;
  logout: () => void;
};

const TOKEN_KEY = 'polskalokalnie-auth-token';
const API_BASE = '/api';

export function getToken(): string | null {
  return window.localStorage.getItem(TOKEN_KEY);
}

/**
 * Cienki wrapper na fetch: dokleja token Bearer i zamienia bledy backendu (pole "message")
 * na wyjatek z czytelnym komunikatem po polsku.
 */
export async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
  const token = getToken();
  const headers = new Headers(options.headers);
  if (options.body && !headers.has('Content-Type')) {
    headers.set('Content-Type', 'application/json');
  }
  if (token) {
    headers.set('Authorization', `Bearer ${token}`);
  }

  const response = await fetch(`${API_BASE}${path}`, { ...options, headers });

  if (!response.ok) {
    let message = 'Wystąpił błąd. Spróbuj ponownie.';
    try {
      const data = await response.json();
      if (data && typeof data.message === 'string' && data.message.trim()) {
        message = data.message;
      }
    } catch {
      // brak/niepoprawne cialo JSON - zostaje komunikat domyslny
    }
    const error = new Error(message) as Error & { status?: number };
    error.status = response.status;
    throw error;
  }

  if (response.status === 204) {
    return undefined as T;
  }
  return (await response.json()) as T;
}

const AuthContext = createContext<AuthContextValue | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [token, setToken] = useState<string | null>(() => getToken());
  const [user, setUser] = useState<AuthUser | null>(null);
  const [loading, setLoading] = useState<boolean>(() => Boolean(getToken()));

  // Po odswiezeniu strony: majac token w localStorage, pobierz aktualny profil.
  useEffect(() => {
    if (!token) {
      setUser(null);
      setLoading(false);
      return;
    }
    let cancelled = false;
    setLoading(true);

    // Sesje konczymy wylacznie wtedy, gdy backend odrzuci token (401/403).
    // Chwilowy blad sieci albo 5xx nie moze wylogowywac uzytkownika - taki blad
    // ponawiamy raz, a token zostaje, wiec kolejne wejscie na strone go odzyska.
    const loadProfile = async (canRetry: boolean): Promise<void> => {
      try {
        const me = await apiFetch<AuthUser>('/auth/me');
        if (!cancelled) {
          setUser(me);
          setLoading(false);
        }
      } catch (error) {
        if (cancelled) {
          return;
        }
        const status = (error as { status?: number }).status;
        if (status === 401 || status === 403) {
          window.localStorage.removeItem(TOKEN_KEY);
          setToken(null);
          setUser(null);
          setLoading(false);
          return;
        }
        if (canRetry) {
          await new Promise((resolve) => { window.setTimeout(resolve, 400); });
          if (!cancelled) {
            await loadProfile(false);
          }
          return;
        }
        setUser(null);
        setLoading(false);
      }
    };

    void loadProfile(true);
    return () => {
      cancelled = true;
    };
  }, [token]);

  const applyAuth = useCallback((auth: AuthResponse) => {
    window.localStorage.setItem(TOKEN_KEY, auth.token);
    setToken(auth.token);
    setUser(auth.user);
    return auth.user;
  }, []);

  const login = useCallback(
    async (email: string, password: string) =>
      applyAuth(await apiFetch<AuthResponse>('/auth/login', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
      })),
    [applyAuth],
  );

  const register = useCallback(
    async (email: string, password: string, fullName: string, details?: RegisterDetails) =>
      applyAuth(await apiFetch<AuthResponse>('/auth/register', {
        method: 'POST',
        body: JSON.stringify({ email, password, fullName, ...details }),
      })),
    [applyAuth],
  );

  const socialLogin = useCallback(
    async (provider: Exclude<AuthProviderName, 'LOCAL'>) =>
      applyAuth(await apiFetch<AuthResponse>('/auth/social', {
        method: 'POST',
        body: JSON.stringify({ provider }),
      })),
    [applyAuth],
  );

  const updateProfile = useCallback(
    async (fullName: string, phone?: string, birthDate?: string, address?: string, contactPreference?: ContactPreference, preferredLanguage?: PreferredLanguage) => {
      const updated = await apiFetch<AuthUser>('/auth/me', {
        method: 'PUT',
        body: JSON.stringify({ fullName, phone, birthDate, address, contactPreference, preferredLanguage }),
      });
      setUser(updated);
      return updated;
    },
    [],
  );

  const logout = useCallback(() => {
    window.localStorage.removeItem(TOKEN_KEY);
    setToken(null);
    setUser(null);
  }, []);

  const value = useMemo<AuthContextValue>(
    () => ({ user, token, loading, login, register, socialLogin, updateProfile, logout }),
    [user, token, loading, login, register, socialLogin, updateProfile, logout],
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth(): AuthContextValue {
  const ctx = useContext(AuthContext);
  if (!ctx) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return ctx;
}
