import { http, HttpResponse } from "msw";
import { fn } from "storybook/test";
import preview from "../../.storybook/preview.tsx";
import { sleep } from "../sleep.ts";
import type { CurrentUser, SessionInfo } from "../types.ts";
import { LoginCard } from "./LoginCard.tsx";

function createMocks(options?: { responseSpeed?: number }) {
  const simulateNetwork = () => sleep(options?.responseSpeed ?? 2000);

  const john: CurrentUser = {
    id: "john",
    email: "john@example.com",
    isVerified: true,
  };

  const mary: CurrentUser = {
    id: "mary",
    email: "mary@example.com",
    isVerified: true,
  };

  const sessionInfo: SessionInfo = {
    createdTs: 123,
    expiresTs: 123,
  };

  return {
    data: { john, mary },
    handlers: {
      refreshTokens: {
        failed: () => {
          return http.post(
            "http://mock.api/v1/sessions/access-tokens",
            async () => {
              await simulateNetwork();
              return HttpResponse.text("Refresh token expired or missing", {
                status: 401,
              });
            },
          );
        },
      },

      getCurrentUser: {
        success: (currentUser: CurrentUser) => {
          return http.get("http://mock.api/v1/users/me", async () => {
            await simulateNetwork();
            return HttpResponse.json(currentUser);
          });
        },

        /**
         * Cookie is valid, but user doesn't exist any more. This can happen
         * after user deletion.
         */
        notFound: () => {
          return http.get("http://mock.api/v1/users/me", async () => {
            await simulateNetwork();
            return HttpResponse.text("User not found", { status: 404 });
          });
        },

        noConnection: () => {
          return http.get("http://mock.api/v1/users/me", async () => {
            return HttpResponse.error();
          });
        },

        unknownFailure: () => {
          return http.get("http://mock.api/v1/users/me", async () => {
            await simulateNetwork();
            return HttpResponse.text("Internal server error", { status: 500 });
          });
        },

        /**
         * Auth cookies no longer valid to make this request.
         */
        notAuthenticated: () => {
          return http.get("http://mock.api/v1/users/me", async () => {
            await simulateNetwork();
            return HttpResponse.text("Not authenticated", { status: 401 });
          });
        },
      },

      createNewSession: {
        success: (currentUser: CurrentUser) => {
          return http.post("http://mock.api/v1/sessions", async () => {
            await simulateNetwork();
            return HttpResponse.json({ currentUser, sessionInfo });
          });
        },

        invalidCredentials: () => {
          return http.post("http://mock.api/v1/sessions", async () => {
            await simulateNetwork();
            return HttpResponse.text("Credentials do not match", {
              status: 401,
            });
          });
        },

        userNotFound: () => {
          return http.post("http://mock.api/v1/sessions", async () => {
            await simulateNetwork();
            return HttpResponse.text("User not found", { status: 404 });
          });
        },

        unknownFailure: () => {
          return http.post("http://mock.api/v1/sessions", async () => {
            await simulateNetwork();
            return HttpResponse.text("Internal server error", { status: 500 });
          });
        },
      },
    },
  };
}

const { data, handlers } = createMocks({ responseSpeed: 700 });

const meta = preview.meta({
  title: "Account/Login Card",
  component: LoginCard,
  tags: ["autodocs"],
  args: {
    description: "Log in to Indie Tabletop Club to enable backup & sync.",
    defaultValues: {},
    onLogin: fn(),
  },
  parameters: {
    msw: {
      handlers: {
        refreshTokens: handlers.refreshTokens.failed(),
      },
    },
  },
});

/**
 * The majority case where no user is stored locally, proactive user session
 * check returns 401, and subsequently correct credentials are provided.
 */
export const Default = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.notAuthenticated(),
        createNewSession: handlers.createNewSession.success(data.john),
      },
    },
  },
});

/**
 * Similar to the default case, but invalid credentials are provided to when
 * attempting to create a new session.
 */
export const InvalidCredentialsOnSubmit = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.notAuthenticated(),
        createNewSession: handlers.createNewSession.invalidCredentials(),
      },
    },
  },
});

/**
 * Similar to the default case, but when credentials are provided, an account
 * email is used that is not currently in the database.
 */
export const UserNotFoundOnSubmit = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.notAuthenticated(),
        createNewSession: handlers.createNewSession.userNotFound(),
      },
    },
  },
});

/**
 * Similar to the default case, but the session creation call returns an error
 * that doesn't have a specific meaning in the context of the login page.
 */
export const UnknownFailureOnSubmit = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.notAuthenticated(),
        createNewSession: handlers.createNewSession.unknownFailure(),
      },
    },
  },
});

/**
 * A case when user is stored locally, but their server session has expired.
 */
export const ReauthenticateSession = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.notAuthenticated(),
        createNewSession: handlers.createNewSession.success(data.john),
      },
    },
  },
});

/**
 * The user is already stored in the app, and proactive user-session check
 * returns the same user (based on ID).
 *
 * The user is directed to app without any further steps.
 */
export const AlreadyLoggedIn = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.success(data.john),
      },
    },
  },
});

/**
 * A user is provided, and proactive user session check returns a different
 * user. This can happen when different users log into separate apps with
 * different credentials.
 */
export const UserMismatch = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.success(data.mary),
      },
    },
  },
});

/**
 * During the proactive user session check, the user is reportd as not found.
 * This means that the tokens are still valid, but the user DB entity is gone.
 * This can happen if a user closed their account.
 */
export const UserNotFound = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.notFound(),
      },
    },
  },
});

/**
 * The proactive user session check has failed due to connection issues.
 */
export const NoConnection = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.noConnection(),
      },
    },
  },
});

/**
 * The proactive user session check has failed due to an error that doesn't
 * carry any special meaning.
 */
export const UnknownFailure = meta.story({
  parameters: {
    msw: {
      handlers: {
        getCurrentUser: handlers.getCurrentUser.unknownFailure(),
      },
    },
  },
});
