import { h, Component, render } from "preact";
import "inputs";
import "./u2f-api-polyfill";

import sha from "sha512";
import { setCookie, getCookie } from "cookie";

let appname = "test";

function Loader() {
   return (
      <div class="loader_box" id="loader">
         <div class="loader"></div>
      </div>
   );
}

class Username extends Component<
   { username: string; onNext: (username: string, salt: string) => void },
   { error: string; loading: boolean }
> {
   username_input: HTMLInputElement;
   constructor() {
      super();
      this.state = { error: undefined, loading: false };
   }

   async onClick() {
      this.setState({ loading: true });
      try {
         let res = await fetch(
            "/api/user/login?type=username&username=" +
               this.username_input.value,
            {
               method: "POST",
            }
         )
            .then((e) => {
               if (e.status !== 200) throw new Error(e.statusText);
               return e.json();
            })
            .then((data) => {
               if (data.error) {
                  return Promise.reject(new Error(data.error));
               }
               return data;
            });
         let salt = res.salt;
         this.props.onNext(this.username_input.value, salt);
      } catch (err) {
         this.setState({
            error: err.message,
         });
      }
      this.setState({ loading: false });
   }

   render() {
      if (this.state.loading) return <Loader />;
      return (
         <div>
            <div class="floating group">
               <input
                  onKeyDown={(e) => {
                     let k = e.keyCode | e.which;
                     if (k === 13) this.onClick();
                     this.setState({ error: undefined });
                  }}
                  type="text"
                  value={
                     this.username_input
                        ? this.username_input.value
                        : this.props.username
                  }
                  autofocus
                  ref={(elm) => (elm ? (this.username_input = elm) : undefined)}
               />
               <span class="highlight"></span>
               <span class="bar"></span>
               <label>Username or Email</label>
               {this.state.error ? (
                  <div class="error"> {this.state.error}</div>
               ) : undefined}
            </div>
            <button
               type="button"
               class="mdc-button mdc-button--raised spanned-btn"
               onClick={() => this.onClick()}
            >
               Next
            </button>
         </div>
      );
   }
}

enum TFATypes {
   OTC,
   BACKUP_CODE,
   YUBI_KEY,
   APP_ALLOW,
}

interface TwoFactors {
   id: string;
   name: string;
   type: TFATypes;
}

class Password extends Component<
   {
      username: string;
      salt: string;
      onNext: (login: Token, special: Token, tfa: TwoFactors[]) => void;
   },
   { error: string; loading: boolean }
> {
   password_input: HTMLInputElement;
   constructor() {
      super();
      this.state = { error: undefined, loading: false };
   }

   async onClick() {
      this.setState({
         loading: true,
      });

      try {
         let pw = sha(this.props.salt + this.password_input.value);
         let { login, special, tfa } = await fetch(
            "/api/user/login?type=password",
            {
               method: "POST",
               body: JSON.stringify({
                  username: this.props.username,
                  password: pw,
               }),
               headers: {
                  "content-type": "application/json",
               },
            }
         )
            .then((e) => {
               if (e.status !== 200) throw new Error(e.statusText);
               return e.json();
            })
            .then((data) => {
               if (data.error) {
                  return Promise.reject(new Error(data.error));
               }
               return data;
            });
         this.props.onNext(login, special, tfa);
      } catch (err) {
         this.setState({ error: err.messagae });
      }
      this.setState({ loading: false });
   }

   render() {
      if (this.state.loading) return <Loader />;
      return (
         <div>
            <div class="floating group">
               <input
                  onKeyDown={(e) => {
                     let k = e.keyCode | e.which;
                     if (k === 13) this.onClick();
                     this.setState({ error: undefined });
                  }}
                  type="password"
                  ref={(elm: HTMLInputElement) => {
                     if (elm) {
                        this.password_input = elm;
                        setTimeout(() => elm.focus(), 200);
                        // elm.focus();
                     }
                  }}
               />
               <span class="highlight"></span>
               <span class="bar"></span>
               <label>Password</label>
               {this.state.error ? (
                  <div class="error"> {this.state.error}</div>
               ) : undefined}
            </div>
            <button
               type="button"
               class="mdc-button mdc-button--raised spanned-btn"
               onClick={() => this.onClick()}
            >
               Login
            </button>
         </div>
      );
   }
}

class TwoFactor extends Component<
   { twofactors: TwoFactors[]; next: (id: string, type: TFATypes) => void },
   {}
> {
   render() {
      let tfs = this.props.twofactors.map((fac) => {
         let name: string;
         switch (fac.type) {
            case TFATypes.OTC:
               name = "Authenticator";
               break;
            case TFATypes.BACKUP_CODE:
               name = "Backup code";
               break;
            case TFATypes.APP_ALLOW:
               name = "Use App: %s";
               break;
            case TFATypes.YUBI_KEY:
               name = "Use Yubikey: %s";
               break;
         }

         name = name.replace("%s", fac.name ? fac.name : "");

         return (
            <li
               onClick={() => {
                  console.log("Click on Solution");
                  this.props.next(fac.id, fac.type);
               }}
            >
               {name}
            </li>
         );
      });
      return (
         <div>
            <h1>Select one</h1>
            <ul>{tfs}</ul>
         </div>
      );
   }
}

// class TFA_YubiKey extends Component<{ id: string, login: Token, special: Token, next: (login: Token, special: Token) => void }, {}> {
//     render() {

//      }
// }

enum Page {
   username,
   password,
   twofactor,
   yubikey,
}

interface Token {
   token: string;
   expires: string;
}

async function apiRequest(
   endpoint: string,
   method: "GET" | "POST" | "DELETE" | "PUT" = "GET",
   body: string = undefined
) {
   return fetch(endpoint, {
      method,
      body,
      credentials: "same-origin",
      headers: {
         "content-type": "application/json",
      },
   })
      .then((e) => {
         if (e.status !== 200) throw new Error(e.statusText);
         return e.json();
      })
      .then((data) => {
         if (data.error) {
            return Promise.reject(new Error(data.error));
         }
         return data;
      });
}

class App extends Component<
   {},
   {
      page: Page;
      username: string;
      salt: string;
      twofactor: TwoFactors[];
      twofactor_id: string;
   }
> {
   login: Token;
   special: Token;
   constructor() {
      super();
      this.state = {
         page: Page.username,
         username: getCookie("username"),
         salt: undefined,
         twofactor: [],
         twofactor_id: null,
      };
   }

   setCookies() {
      setCookie(
         "login",
         this.login.token,
         new Date(this.login.expires).toUTCString()
      );
      setCookie(
         "special",
         this.special.token,
         new Date(this.special.expires).toUTCString()
      );
   }

   finish() {
      this.setCookies();
      let d = new Date();
      d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days
      setCookie("username", this.state.username, d.toUTCString());
      let url = new URL(window.location.href);
      let state = url.searchParams.get("state");
      let red = "/";

      if (state) {
         let base64 = url.searchParams.get("base64");
         if (base64) red = atob(state);
         else red = state;
      }
      window.location.href = red;
   }

   render() {
      let cont;
      switch (this.state.page) {
         case Page.username:
            cont = (
               <Username
                  username={this.state.username}
                  onNext={(username, salt) => {
                     this.setState({ username, salt, page: Page.password });
                     localStorage.setItem("username", username);
                  }}
               />
            );
            break;
         case Page.password:
            cont = (
               <Password
                  username={this.state.username}
                  salt={this.state.salt}
                  onNext={(login, special, twofactor) => {
                     this.login = login;
                     this.special = special;
                     this.setCookies();

                     if (!twofactor) {
                        this.finish();
                     } else {
                        this.setState({ twofactor, page: Page.twofactor });
                     }
                  }}
               />
            );
            break;
         case Page.twofactor:
            cont = (
               <TwoFactor
                  twofactors={this.state.twofactor}
                  next={async (id, type) => {
                     if (type === TFATypes.YUBI_KEY) {
                        let { request } = await apiRequest(
                           "/api/user/twofactor/yubikey",
                           "GET"
                        );
                        console.log(request);
                        (window as any).u2f.sign(
                           request.appId,
                           [request.challenge],
                           [request],
                           async (response) => {
                              let res = await apiRequest(
                                 "/api/user/twofactor/yubikey",
                                 "PUT",
                                 JSON.stringify({ response })
                              );
                              if (res.success) {
                                 this.login.expires = res.login_exp;
                                 this.special.expires = res.special_exp;
                                 this.finish();
                              }
                           }
                        );
                     }
                  }}
               />
            );
            break;
         // case Page.yubikey:
         //     cont = <TFA_YubiKey id={this.state.twofactor_id} login={this.login} special={this.special} next={(login, special) => {
         //         this.login = login;
         //         this.special = special;
         //         this.finish()
         //     }} />
         //     break;
      }
      return (
         <div>
            <header>
               <h1>Login</h1>
            </header>
            <form action="JavaScript:void(0)">{cont}</form>
            <footer>
               <p>Powered by {appname}</p>
            </footer>
         </div>
      );
   }
}

document.addEventListener(
   "DOMContentLoaded",
   function () {
      render(<App />, document.body.querySelector("#content"));
   },
   false
);
