import axios from "axios";
import {
  Appointment,
  CRMBackend,
  HubSpotApiResponse,
} from "../crm.interfaces.js";
import winston from "winston";
import { GoogleCalendarBackend } from "./GoogleCalendarBackend.js";
import { CalendlyBackend } from "./CalendlyBackend.js";

const HUBSPOT_API_BASE_URL = "https://api.hubspot.com";

export class HubSpotBackend implements CRMBackend {
  private logger: winston.Logger;
  private hubspotApiKey: string;
  private calenderBackend: GoogleCalendarBackend | CalendlyBackend;

  private hubspotOwners: any[] = [];
  private employee: string;

  constructor(backend?: string, hubspotOwners?: any[], employee?: string) {
    this.hubspotOwners = hubspotOwners || [];
    this.employee = employee || "";
    this.logger = winston.createLogger({
      level: "info",
      format: winston.format.json(),
      defaultMeta: { service: "crm-service" },
      transports: [
        new winston.transports.Console({
          format: winston.format.simple(),
        }),
      ],
    });
    this.hubspotApiKey = (process.env.HUBSPOT_API_KEY as string) || "";
    switch (backend) {
      case "google":
        this.calenderBackend = new GoogleCalendarBackend();
        break;
      case "calendly":
        this.calenderBackend = new CalendlyBackend();
        break;
      default:
        throw new Error("Invalid backend specified");
    }
  }

  private getHeaders() {
    return {
      headers: {
        Authorization: `Bearer ${this.hubspotApiKey}`,
        "Content-Type": "application/json",
      },
    };
  }

  async fetchAvailableSlots(
    timestamp: string,
    attendees: string[],
  ): Promise<string[]> {
    return this.calenderBackend.fetchAvailableSlots(timestamp, attendees);
  }

  async fetchAppointmentTypes(): Promise<string[]> {
    // TODO: Implement fetching appointment types from HubSpot
    return ["Consultation", "Meeting", "Demo"];
  }

  async getContactIdByName(contactName: string): Promise<string | null> {
    const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/contacts/search`;
    const [firstName, lastName] = contactName.split(" "); // Split the name into first and last name
    const data = {
      filterGroups: [
        {
          filters: [
            {
              propertyName: "firstname",
              operator: "EQ",
              value: firstName,
            },
            {
              propertyName: "lastname",
              operator: "EQ",
              value: lastName,
            },
          ],
        },
      ],
    };

    try {
      const response = await axios.post(url, data, {
        headers: {
          Authorization: `Bearer ${this.hubspotApiKey}`,
          "Content-Type": "application/json",
        },
      });

      // Check if the contact exists and return the ID
      if ((response.data as { results: any[] }).results.length > 0) {
        const contactId = (response.data as { results: any[] }).results[0].id; // Get the first matching contact's ID
        this.logger.info(`Contact found. ID: ${contactId}`);
        return contactId; // Return the contact ID
      } else {
        this.logger.info("Contact does not exist");
        return null; // Contact not found, return null
      }
    } catch (error: any) {
      this.logger.error(
        "Error fetching contact:",
        error.response?.data || error.message,
      );
      throw new Error("Failed to check if contact exists");
    }
  }

  async createContact(contactName: string): Promise<string> {
    const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/contacts`;
    const data = {
      properties: {
        firstname: contactName.split(" ")[0],
        lastname: contactName.split(" ")[1], // Assumes name is in "First Last" format
      },
    };
    try {
      const response = (await axios.post(url, data, this.getHeaders())) as {
        data: { id: string };
      };
      this.logger.info(response.data);
      if (response.data?.id) {
        return response.data.id;
      } else {
        throw new Error("Failed to create contact");
      }
    } catch (error: any) {
      throw new Error("Failed to create contact: " + error.message);
    }
  }

  async appointmentCreationCall(
    appointment: Appointment,
    contactId: string,
    timestampMs: number,
    employee: any,
  ): Promise<HubSpotApiResponse> {
    const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/meetings`;
    const data = {
      associations: [
        {
          types: [
            {
              associationCategory: "HUBSPOT_DEFINED",
              associationTypeId: 200,
            },
          ],
          to: {
            id: contactId,
          },
        },
      ],
      properties: {
        hs_timestamp: appointment.timestamp,
        hs_meeting_start_time: timestampMs, // Start time in milliseconds
        hs_meeting_end_time: timestampMs + 3600000, // End time (default: 1 hour later)
        hubspot_owner_id: employee?.id,
        hs_meeting_title:
          appointment.meetingTitle ?? `Meeting w/ ${appointment.contactName}`,
        hs_internal_meeting_notes: appointment.meetingNotes,
        hs_meeting_location: "Remote",
        hs_meeting_outcome: "SCHEDULED",
      },
    };
    try {
      const response = await axios.post(url, data, this.getHeaders());
      return response.data as HubSpotApiResponse;
    } catch (error: any) {
      this.logger.error(
        "Error booking appointment:",
        error.response?.data || error.message,
      );
      throw new Error("Failed to book appointment.");
    }
  }

  async bookAppointment(appointment: Appointment): Promise<string> {
    const employee = this.hubspotOwners.find(
      (owner) => owner.lastName === appointment.employeeName,
    );
    const timestampMs = new Date(appointment.timestamp).getTime(); // Convert to milliseconds
    const contactId =
      (await this.getContactIdByName(appointment.contactName)) ||
      (await this.createContact(appointment.contactName));
    const apiReponse = await this.appointmentCreationCall(
      appointment,
      contactId,
      timestampMs,
      employee,
    );
    const meetingUrl = `https://app.hubspot.com/meetings/${apiReponse.id}`;
    return meetingUrl;
  }

  async modifyAppointment(
    id: string,
    updatedInfo: Partial<Appointment>,
  ): Promise<Appointment> {
    const url = `${HUBSPOT_API_BASE_URL}/engagements/v1/engagements/${id}`;

    // Prepare the data for the API request
    const data: any = {
      engagement: {
        type: "MEETING",
      },
      metadata: {},
    };

    // Update timestamp if provided
    if (updatedInfo.timestamp) {
      data.engagement.timestamp = new Date(updatedInfo.timestamp).getTime();
      data.metadata.startTime = new Date(updatedInfo.timestamp).getTime();
      // Assuming a default 1-hour duration
      data.metadata.endTime =
        new Date(updatedInfo.timestamp).getTime() + 3600000;
    }

    // Update notes if provided
    if (updatedInfo.meetingNotes) {
      data.metadata.body = updatedInfo.meetingNotes;
    }

    // Update type if provided
    if (updatedInfo.type) {
      data.metadata.meetingType = updatedInfo.type;
    }

    // Update status if provided
    if (updatedInfo.status) {
      data.metadata.status = updatedInfo.status;
    }

    try {
      // Make the API call to update the engagement
      await axios.patch(url, data, this.getHeaders());
      this.logger.info(`Modified appointment with ID: ${id}`);
      // Fetch the updated appointment details
      const updatedAppointment = await this.findAppointmentById(id);

      if (!updatedAppointment) {
        throw new Error("Failed to retrieve updated appointment");
      }

      return updatedAppointment;
    } catch (error) {
      this.logger.error("Error modifying appointment:", error);
      throw new Error("Failed to modify appointment");
    }
  }

  async findAppointmentByContactName(name: string): Promise<Appointment> {
    const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/meetings/search`; // Correct endpoint
    const data = {
      filterGroups: [
        {
          filters: [
            {
              propertyName: "hs_meeting_title",
              operator: "CONTAINS_TOKEN",
              value: name,
            },
          ],
        },
      ],
    };

    try {
      // Ensure this is a POST request
      const response = await axios.post(url, data, this.getHeaders());
      const responseData = response.data as { results: any[] };
      return responseData.results.map((meeting: any) => ({
        id: meeting.id,
        timestamp: meeting.properties.hs_meeting_start_time,
        meetingNotes: meeting.properties.hs_meeting_body || "",
        type: "Meeting",
        status: "Scheduled",
        contactName: name,
        employeeName: this.employee,
      }))[0] as Appointment;
    } catch (error: any) {
      this.logger.error(
        "Error searching for appointments:",
        error.response?.data || error.message,
      );
      throw new Error("Failed to search for appointments by name");
    }
  }

  async findAppointmentByTimestamp(
    timestamp: string,
  ): Promise<Appointment | null> {
    const url = `${HUBSPOT_API_BASE_URL}/engagements/v1/engagements`;
    const response = await axios.get(url, this.getHeaders());

    const appointment = (response.data as { results: any[] }).results.find(
      (meeting: any) =>
        new Date(meeting.metadata.startTime).toISOString() === timestamp,
    );

    return {
      id: appointment.engagement.id,
      timestamp,
      meetingNotes: appointment.metadata.body,
      type: "Meeting",
      status: "Scheduled",
      contactName: "",
      employeeName: this.employee,
    } as Appointment;
  }

  async findAppointmentById(id: string): Promise<Appointment | null> {
    const url = `${HUBSPOT_API_BASE_URL}/engagements/v1/engagements/${id}`;
    try {
      const response = await axios.get(url, this.getHeaders());
      const meeting = response.data as {
        metadata: { startTime: string; body: string };
      };

      return {
        id,
        timestamp: new Date(meeting.metadata.startTime).toISOString(),
        meetingNotes: meeting.metadata.body,
        type: "Meeting",
        status: "Scheduled",
        contactName: "",
        employeeName: this.employee,
      };
    } catch (error: any) {
      if (error.response?.status === 404) return null;
      throw error;
    }
  }

  async createEventToSms(appointment: Appointment): Promise<string> {
    throw new Error("Method not implemented.");
  }
}
