import { ViewpointApiClient, ViewpointApiWriteClient } from "../core";
import type {
  Account,
  Contract,
  CRMData,
  ExternalMembership,
  Member,
  MemberOpenLog,
} from "../type";

/**
 * How an account-level update is sent to Viewpoint.
 *
 * Viewpoint documents `POST /Member` for creation and `PUT /Contact` for a contact,
 * but the account-level update is not in the copy of the spec this repo has. The
 * shape below mirrors the contact update — full record, PUT, natural key in the
 * body — because that is the convention every other Viewpoint write here follows.
 *
 * Kept as constants so correcting it against the real spec is a one-line change and
 * does not mean re-reading the call site.
 */
const ACCOUNT_UPDATE_PATH = "/Member";
const ACCOUNT_UPDATE_METHOD = "PUT";

export class ViewpointMember {
  private client: <TResponse>(
    path: string,
    init?: RequestInit,
  ) => Promise<TResponse | null>;
  // Mutations only. Unlike `client`, this throws rather than returning null, so a
  // failed write cannot be mistaken for "Viewpoint returned nothing".
  private writeClient: <TResponse>(
    path: string,
    init?: RequestInit,
  ) => Promise<TResponse>;
  private _crmData: {
    relationship: {
      fieldID: number;
    };
    anniversary: {
      fieldID: number;
    };
  };
  constructor() {
    const baseURL = process.env.VIEWPOINT_API!;
    const token = process.env.VIEWPOINT_API_KEY!;

    if (!baseURL || !token) {
      throw new Error("Missing API or API Token");
    }
    this.client = ViewpointApiClient(baseURL, token);
    this.writeClient = ViewpointApiWriteClient(baseURL, token);
    this._crmData = {
      relationship: {
        fieldID: 4531,
      },
      anniversary: {
        fieldID: 10,
      },
    };
  }

  getCRMDataMap() {
    return this._crmData;
  }

  getGuestMemberReferrerCRMFieldID(isLive: boolean) {
    return isLive ? 4969 : 4957;
  }

  // Retrieve all member accounts by email address
  async findByEmail(email: string) {
    try {
      const response = await this.client<Account[]>("/Member/search", {
        method: "POST",
        body: JSON.stringify({ email: email }),
      });
      if (!response) {
        return [];
      }
      return response;
    } catch (err) {
      console.log(err, "err");

      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  // Retrieve a member account by an account Id
  async findByAccountID(id: string) {
    try {
      const response = await this.client<Account>(`/Member/${id}`, {
        method: "GET",
      });
      if (!response) {
        return null;
      }
      return response;
    } catch (err) {
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  /**
   * Moves an account to a different status in Viewpoint.
   *
   * Viewpoint is the system of record for account status, so this is the write that
   * actually retires an account — the console's own database only mirrors it.
   *
   * The current account is read first and sent back whole with the status replaced,
   * rather than sending `{ AccountID, AccountStatusID }` alone. Viewpoint's contact
   * update replaces the record with what it is given, so a partial body risks
   * blanking the fields it omits. The read also gives the caller the previous status
   * to record, which a blind write could not.
   *
   * Returns the status it moved away from. Throws if the account does not exist or
   * if Viewpoint refuses the write — never returns null, because a caller about to
   * mirror this into its own database must not treat a failure as a no-op.
   */
  async updateAccountStatus(payload: {
    AccountID: string;
    AccountStatusID: number;
  }) {
    const account = await this.findByAccountID(payload.AccountID);
    if (!account) {
      throw new Error(`Viewpoint has no account ${payload.AccountID}`);
    }

    if (account.AccountStatusID === payload.AccountStatusID) {
      return {
        account,
        previousStatusID: account.AccountStatusID,
        previousStatus: account.AccountStatus,
        changed: false,
      };
    }

    const updated = await this.writeClient<Account>(ACCOUNT_UPDATE_PATH, {
      method: ACCOUNT_UPDATE_METHOD,
      body: JSON.stringify({
        ...account,
        AccountStatusID: payload.AccountStatusID,
      }),
    });

    return {
      account: updated ?? account,
      previousStatusID: account.AccountStatusID,
      previousStatus: account.AccountStatus,
      changed: true,
    };
  }

  async findByContactID(id: string) {
    try {
      const response = await this.client<Member>(`/Contact/${id}`, {
        method: "GET",
      });
      if (!response) {
        return null;
      }
      return response;
    } catch (err) {
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  async findExternalMembershipsByAccountID(id: string) {
    try {
      const response = await this.client<ExternalMembership[]>(
        `/Member/${id}/externalmembership`,
        {
          method: "GET",
        },
      );
      if (!response) {
        return [];
      }
      return response;
    } catch (err) {
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  async updateContactByID(payload: {
    AccountID: string;
    ContactID: number;
    FirstName?: string;
    LastName?: string;
    MailName?: string | null;
    Email?: string;
    Mobile?: string | null;
    AddressLine1?: string | null;
    AddressLine2?: string | null;
    AddressState?: string | null;
    AddressCity?: string | null;
    AddrCountry?: string | null;
    Postcode?: string | null;
    Nationality?: string | null;
    DateOfBirth?: string;
    IsPrimary: boolean;
    IsOwner: boolean;
    IsFamily: boolean;
    IsAuthority: boolean;
    Active: boolean;
  }) {
    try {
      const response = await this.client<Member>(`/Contact`, {
        method: "PUT",
        body: JSON.stringify(payload),
      });
      if (!response) {
        return null;
      }
      return response;
    } catch (err) {
      console.log(err);
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  async createContact(payload: {
    AccountID: string;
    FirstName?: string;
    LastName?: string;
    MailName?: string | null;
    Email?: string;
    Mobile?: string | null;
    AddressLine1?: string | null;
    AddressLine2?: string | null;
    AddressState?: string | null;
    AddressCity?: string | null;
    AddrCountry?: string | null;
    Postcode?: string | null;
    Nationality?: string | null;
    DateOfBirth?: string;
    IsPrimary: boolean;
    IsOwner: boolean;
    IsFamily: boolean;
    IsAuthority: boolean;
    Active: boolean;
    CRMData: CRMData[];
  }) {
    try {
      const response = await this.client<Member>(`/Contact`, {
        method: "POST",
        body: JSON.stringify(payload),
      });
      if (!response) {
        return null;
      }
      return response;
    } catch (err) {
      console.log(err);
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  async createMember(payload: {
    AccountTypeID: number;
    ServiceCenterID: number;
    AccountStatusID: number;
    DefaultChargeCenter: number;
    JoinedDate: string;
    CRMData: CRMData[];
    Owners: {
      FirstName: string;
      LastName: string;
      MailName: string;
      Email: string;
      Mobile?: string | null;
      AddressLine1?: string | null;
      AddressLine2?: string | null;
      AddressState?: string | null;
      AddressCity?: string | null;
      AddrCountry: string | null;
      Postcode?: string | null;
      Nationality?: string | null;
      DateOfBirth?: string;
      IsPrimary: boolean;
      IsOwner: boolean;
      IsFamily: boolean;
      IsAuthority: boolean;
      Active: boolean;
      Deleted: boolean;
      CRMData: CRMData[];
    }[];
  }) {
    try {
      const response = await this.client<Account>(`/Member`, {
        method: "POST",
        body: JSON.stringify(payload),
      });
      if (!response) {
        return null;
      }
      return response;
    } catch (err) {
      console.log(err);
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  // Create an open log in Viewpoint on behalf of member
  async createLog(
    logData: {
      accountID: string;
    } & Pick<MemberOpenLog, "comment" | "type" | "subject">,
  ) {
    try {
      const account = await this.findByAccountID(logData.accountID);
      if (!account) {
        console.log("Invalid account ID");
        return;
      }

      if (!account.ServiceCenterID) {
        console.log("Missing service center ID");
        return;
      }

      let assignedToUser: string | null = null;

      if (account.ServiceCenterID === 38994) {
        assignedToUser = "vp2328";
      } else if (account.ServiceCenterID === 38995) {
        assignedToUser = "vp1908";
      } else if (account.ServiceCenterID === 95310) {
        assignedToUser = "vp2329";
      }

      if (!assignedToUser) {
        console.log("Failed to fetch assigned service user");
        return;
      }

      const now = new Date();
      const dueDate = new Date();
      dueDate.setDate(now.getDate() + 7);

      const payload = {
        LogType: logData.type,
        UserLogType: 0,
        LogStatus: "OPEN",
        AssignedToUser: assignedToUser,
        LogComment: logData.comment,
        Subject: logData.subject,
        LogSourceID: 95010,
        UserDate: now.toISOString(),
        DueDate: dueDate.toISOString(),
      };

      const response = await this.client(`/Member/${logData.accountID}/log`, {
        method: "POST",
        body: JSON.stringify(payload),
      });
      return response;
    } catch (err) {
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  async uploadDocument(
    memberNumber: string,
    payload: {
      description?: string;
      type: number;
      file_type: number;
      source_type: number;
      file_name: string;
      data: string;
    },
  ) {
    try {
      const body = {
        Description: payload.description ?? "",
        DocumentType: payload.type,
        DocumentFileType: payload.file_type,
        DocumentSourceType: payload.source_type,
        DocumentDate: new Date().toISOString(),
        SourceFileName: payload.file_name,
        Base64Data: payload.data,
      };
      const response = await this.client(`/Member/1199943/document`, {
        method: "POST",
        body: JSON.stringify(body),
      });
      return response;
    } catch (err) {
      throw err instanceof Error ? err : new Error(String(err));
    }
  }

  async getContracts(accountID: string) {
    try {
      const response = await this.client<Contract[]>(
        `/Member/${accountID}/contracts`,
        {
          method: "GET",
        },
      );
      if (!response) {
        return [];
      }
      return response;
    } catch (err) {
      throw err instanceof Error ? err : new Error(String(err));
    }
  }
}
