import { Injectable } from "@nestjs/common";

// SmsType enum to match Prisma schema
type SmsType = "DOMESTIC" | "INTERNATIONAL";

export interface DomesticCallRecord {
  account: string;
  callDate: Date;
  callTime: string;
  calledTo: string;
  location: string | null;
  durationSec: number;
  chargeYen: number;
  month: string;
}

export interface InternationalCallRecord {
  account: string;
  callDate: Date;
  startTime: string;
  stopTime: string | null;
  country: string | null;
  calledTo: string;
  durationSec: number;
  chargeYen: number;
  month: string;
}

export interface SmsRecord {
  account: string;
  smsDate: Date;
  smsTime: string;
  sentTo: string;
  smsType: SmsType;
  month: string;
}

export interface ParsedTalkDetail {
  domestic: DomesticCallRecord[];
  international: InternationalCallRecord[];
  skipped: number;
  errors: number;
}

export interface ParsedSmsDetail {
  records: SmsRecord[];
  skipped: number;
  errors: number;
}

/**
 * Parsed columns from a talk detail CSV line
 */
interface TalkDetailColumns {
  phoneNumber: string;
  dateStr: string;
  timeStr: string;
  calledTo: string;
  callType: string;
  location: string;
  durationStr: string;
  tokensStr: string;
  altChargeStr: string;
}

/**
 * Service for parsing call history CSV files from Freebit SFTP.
 * Handles the specific CSV formats for talk detail and SMS detail files.
 *
 * Talk Detail CSV Columns:
 * 1. Customer phone number
 * 2. Date (YYYYMMDD)
 * 3. Start time (HHMMSS)
 * 4. Called to phone number
 * 5. dome/tointl (call type)
 * 6. Location
 * 7. Duration (MMSST format - minutes, seconds, tenths)
 * 8. Tokens (each token = 10 yen)
 * 9. Alternative charge (if location is "他社")
 *
 * SMS Detail CSV Columns:
 * 1. Customer phone number
 * 2. Date (YYYYMMDD)
 * 3. Start time (HHMMSS)
 * 4. SMS sent to phone number
 * 5. dome/tointl
 * 6. SMS type (ＳＭＳ or 国際ＳＭＳ)
 */
@Injectable()
export class SimCallHistoryParserService {
  /**
   * Parse talk detail CSV content into domestic and international call records
   */
  parseTalkDetailCsv(content: string, month: string): ParsedTalkDetail {
    const domestic: DomesticCallRecord[] = [];
    const international: InternationalCallRecord[] = [];
    let skipped = 0;
    let errors = 0;

    const lines = content.split(/\r?\n/).filter(line => line.trim());

    for (const line of lines) {
      try {
        const result = this.parseTalkDetailLine(line, month);
        if (result.type === "domestic") {
          domestic.push(result.record);
        } else if (result.type === "international") {
          international.push(result.record);
        } else {
          skipped++;
        }
      } catch {
        errors++;
      }
    }

    return { domestic, international, skipped, errors };
  }

  /**
   * Parse a single talk detail line and return the appropriate record type
   */
  private parseTalkDetailLine(
    line: string,
    month: string
  ):
    | { type: "domestic"; record: DomesticCallRecord }
    | { type: "international"; record: InternationalCallRecord }
    | { type: "skip" } {
    const parsed = this.extractTalkDetailColumns(line);
    if (!parsed) {
      return { type: "skip" };
    }

    const callDate = this.parseDate(parsed.dateStr);
    if (!callDate) {
      return { type: "skip" };
    }

    const baseFields = this.buildCallBaseFields(parsed);

    if (this.isDomesticCall(parsed.callType)) {
      return {
        type: "domestic",
        record: {
          ...baseFields,
          callDate,
          callTime: parsed.timeStr,
          location: parsed.location || null,
          month,
        },
      };
    }

    if (this.isInternationalCall(parsed.callType)) {
      return {
        type: "international",
        record: {
          ...baseFields,
          callDate,
          startTime: parsed.timeStr,
          stopTime: null,
          country: parsed.location || null,
          month,
        },
      };
    }

    return { type: "skip" };
  }

  /**
   * Extract columns from a talk detail CSV line
   */
  private extractTalkDetailColumns(line: string): TalkDetailColumns | null {
    const columns = this.parseCsvLine(line);
    if (columns.length < 8) {
      return null;
    }

    const [
      phoneNumber = "",
      dateStr = "",
      timeStr = "",
      calledTo = "",
      callType = "",
      location = "",
      durationStr = "",
      tokensStr = "",
      altChargeStr = "",
    ] = columns;

    return {
      phoneNumber,
      dateStr,
      timeStr,
      calledTo,
      callType,
      location,
      durationStr,
      tokensStr,
      altChargeStr,
    };
  }

  /**
   * Build common call record fields from parsed columns
   */
  private buildCallBaseFields(cols: TalkDetailColumns): {
    account: string;
    calledTo: string;
    durationSec: number;
    chargeYen: number;
  } {
    return {
      account: this.cleanPhoneNumber(cols.phoneNumber),
      calledTo: this.cleanPhoneNumber(cols.calledTo),
      durationSec: this.parseDuration(cols.durationStr),
      chargeYen: this.parseCharge(cols.location, cols.tokensStr, cols.altChargeStr),
    };
  }

  /**
   * Check if call type is domestic
   */
  private isDomesticCall(callType: string): boolean {
    return callType === "dome" || callType === "domestic";
  }

  /**
   * Check if call type is international
   */
  private isInternationalCall(callType: string): boolean {
    return callType === "tointl" || callType === "international";
  }

  /**
   * Parse SMS detail CSV content
   */
  parseSmsDetailCsv(content: string, month: string): ParsedSmsDetail {
    const records: SmsRecord[] = [];
    let skipped = 0;
    let errors = 0;

    const lines = content.split(/\r?\n/).filter(line => line.trim());

    for (const line of lines) {
      try {
        const columns = this.parseCsvLine(line);

        if (columns.length < 6) {
          skipped++;
          continue;
        }

        const [phoneNumber = "", dateStr = "", timeStr = "", sentTo = "", , smsTypeStr = ""] =
          columns;

        // Parse date
        const smsDate = this.parseDate(dateStr);
        if (!smsDate) {
          skipped++;
          continue;
        }

        // Clean account number
        const account = this.cleanPhoneNumber(phoneNumber);

        // Clean sent-to number
        const cleanSentTo = this.cleanPhoneNumber(sentTo);

        // Determine SMS type
        const smsType: SmsType = smsTypeStr.includes("国際") ? "INTERNATIONAL" : "DOMESTIC";

        records.push({
          account,
          smsDate,
          smsTime: timeStr,
          sentTo: cleanSentTo,
          smsType,
          month,
        });
      } catch {
        errors++;
      }
    }

    return { records, skipped, errors };
  }

  /**
   * Parse a CSV line handling quoted fields and escaped quotes
   */
  private parseCsvLine(line: string): string[] {
    const normalizedLine = line.replace(/\r$/, "").replace(/^\uFEFF/, "");
    const result: string[] = [];
    let current = "";
    let inQuotes = false;

    for (let i = 0; i < normalizedLine.length; i++) {
      const char = normalizedLine[i];
      if (char === '"') {
        if (inQuotes && normalizedLine[i + 1] === '"') {
          current += '"';
          i++;
          continue;
        }
        inQuotes = !inQuotes;
        continue;
      }
      if (char === "," && !inQuotes) {
        result.push(current.trim());
        current = "";
        continue;
      }
      current += char;
    }
    result.push(current.trim());

    return result;
  }

  /**
   * Parse YYYYMMDD date string to Date object
   */
  private parseDate(dateStr: string): Date | null {
    if (!dateStr || dateStr.length < 8) return null;

    const clean = dateStr.replace(/[^0-9]/g, "");
    if (clean.length < 8) return null;

    const year = Number.parseInt(clean.slice(0, 4), 10);
    const month = Number.parseInt(clean.slice(4, 6), 10) - 1;
    const day = Number.parseInt(clean.slice(6, 8), 10);

    if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) return null;

    return new Date(year, month, day);
  }

  /**
   * Parse duration string (MMSST format) to seconds
   */
  private parseDuration(durationStr: string): number {
    const durationVal = durationStr.padStart(5, "0");
    const minutes = Number.parseInt(durationVal.slice(0, -3), 10) || 0;
    const seconds = Number.parseInt(durationVal.slice(-3, -1), 10) || 0;
    return minutes * 60 + seconds;
  }

  /**
   * Parse charge from tokens or alternative charge
   */
  private parseCharge(
    location: string | undefined,
    tokensStr: string,
    altChargeStr: string | undefined
  ): number {
    if (location && location.includes("他社") && altChargeStr) {
      return Number.parseInt(altChargeStr, 10) || 0;
    }
    return (Number.parseInt(tokensStr, 10) || 0) * 10;
  }

  /**
   * Clean phone number by removing dashes and spaces
   */
  private cleanPhoneNumber(phone: string): string {
    return phone.replace(/[-\s]/g, "");
  }
}
