import { API_URL, decodeJWT, mobxFetch } from "@shared";
import { canRead as checkCanRead, canWrite as checkCanWrite } from "../../lib/permissions";
import { makeAutoObservable, runInAction } from "mobx";
import axios, { AxiosError } from "axios";
import { User, UserCity } from "../UserStore";
import { getMeApi, getMeCitiesApi } from "./api";

type LoginResponse = {
  token: string;
  user: Pick<User, "id" | "name" | "email" | "is_admin" | "cities">;
};

class AuthStore {
  payload: LoginResponse | null = null;
  token: string | null = null;
  isLoading = false;
  error: string | null = null;

  constructor() {
    makeAutoObservable(this);
    this.initialize();
  }

  private initialize() {
    const storedToken = localStorage.getItem("token") || undefined;
    const decoded = decodeJWT(storedToken);

    if (decoded) {
      this.payload = decoded;
    } else {
      this.logout();
    }
  }

  private setAuthToken(token: string) {
    localStorage.setItem("token", token);
  }

  login = async (email: string, password: string) => {
    this.isLoading = true;
    this.error = null;

    try {
      const response = await axios.post<LoginResponse>(
        `${API_URL}/auth/login`,
        {
          email,
          password,
        },
      );

      const data = response.data;

      runInAction(() => {
        this.setAuthToken(data.token);
        this.payload = {
          ...response.data.user,
          // @ts-ignore
          user_id: response.data.user.id,
        };
        this.error = null;
      });
    } catch (error) {
      if (error instanceof AxiosError) {
        this.error =
          error.response?.data?.message || "Ошибка при входе в систему";
      } else {
        this.error = "Неизвестная ошибка при входе в систему";
      }
      throw new Error(this.error ?? "Неизвестная ошибка при входе в систему");
    } finally {
      this.isLoading = false;
    }
  };

  logout = () => {
    localStorage.removeItem("token");

    this.payload = null;
    this.error = null;
  };

  get isAuthenticated() {
    return this.payload !== null;
  }

  get user() {
    return this.payload?.user;
  }

  get isAdmin(): boolean {
    return (
      this.me?.is_admin === true ||
      (this.me?.roles ?? []).includes("admin")
    );
  }

  me: User | null = null;
  meLoading = false;
  meError: string | null = null;

  meCities: { ru: UserCity[]; en: UserCity[]; zh: UserCity[] } = {
    ru: [],
    en: [],
    zh: [],
  };

  getMeAction = mobxFetch<void, User, AuthStore>({
    store: this,
    value: "me",
    loading: "meLoading",
    error: "meError",
    fn: getMeApi,
    onSuccess: () => {
      this.fetchMeCities();
    },
  });

  fetchMeCities = async () => {
    const cities = await getMeCitiesApi();
    runInAction(() => {
      this.meCities = cities;
    });
  };

  canWrite = (resource: string): boolean => {
    const roles = this.me?.roles ?? [];

    if (roles.includes("admin")) {
      return true;
    }

    if (resource === "map") {
      return roles.some((role) =>
        ["routes_rw", "stations_rw", "sights_rw"].includes(role),
      );
    }

    return checkCanWrite(roles, resource);
  };

  hasRole = (role: string): boolean => {
    const roles = this.me?.roles ?? [];
    return roles.includes("admin") || roles.includes(role);
  };

  canRead = (resource: string): boolean => {
    if (resource === "map") {
      return this.canWrite("map");
    }
    return checkCanRead(this.me?.roles, resource);
  };

  canAccess = (permission: string): boolean => {
    if (permission.includes("_")) {
      return this.hasRole(permission);
    }
    return this.canRead(permission);
  };
}

export const authStore = new AuthStore();
