import { Injectable, UnauthorizedException } from '@nestjs/common';
import * as crypto from 'crypto';
import * as SMB2_MODULE from '@marsaud/smb2';

const SMB2: any = (SMB2_MODULE as any).default || SMB2_MODULE;

@Injectable()
export class SmbService {
  private sessions = new Map<string, { username: string; password: string }>();
  private clientCache = new Map<string, any>(); // cache key: userId_shareName
  private readonly serverIp = process.env.SMB_SERVER_IP || '173.22.1.3';
  private readonly domain = process.env.SMB_DOMAIN || '';

  saveSession(userId: string, username: string, password: string) {
    this.sessions.set(userId, { username, password });
  }

  // Parse "IT\Folder\file.txt" menjadi { shareName: "IT", internalPath: "Folder\file.txt" }
  private parsePath(fullPath: string) {
    const firstSlashIdx = fullPath.indexOf('\\');
    if (firstSlashIdx === -1) {
      return { shareName: fullPath, internalPath: '' };
    }
    return {
      shareName: fullPath.substring(0, firstSlashIdx),
      internalPath: fullPath.substring(firstSlashIdx + 1)
    };
  }

  async getClient(userId: string, shareName: string): Promise<any> {
    const cacheKey = `${userId}_${shareName}`;
    if (this.clientCache.has(cacheKey)) {
      return this.clientCache.get(cacheKey);
    }

    const session = this.sessions.get(userId);
    if (!session) {
      throw new UnauthorizedException('Sesi SMB tidak ditemukan. Silakan login ulang.');
    }

    let domain = this.domain;
    let actualUsername = session.username;

    if (actualUsername.includes('\\')) {
      const parts = actualUsername.split('\\');
      domain = parts[0];
      actualUsername = parts[1];
    }

    const client = new SMB2({
      share: `\\\\${this.serverIp}\\${shareName}`,
      domain: domain,
      username: actualUsername,
      password: session.password,
      autoCloseTimeout: 0
    });

    this.clientCache.set(cacheKey, client);
    return client;
  }

  async readdir(userId: string, fullPath: string): Promise<string[]> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.readdir(internalPath, (err: any, files: string[]) => {
        if (err) reject(err);
        else resolve(files);
      });
    });
  }

  async stat(userId: string, fullPath: string): Promise<any> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.stat(internalPath, (err: any, stats: any) => {
        if (err) reject(err);
        else resolve(stats);
      });
    });
  }

  async readdirWithStats(userId: string, fullPath: string): Promise<any[]> {
    const files = await this.readdir(userId, fullPath);
    
    const statsPromises = files
      .filter(file => file !== '.' && file !== '..')
      .map(async (fileName) => {
        const itemFullPath = fullPath ? `${fullPath}\\${fileName}` : fileName;
        try {
          const stats = await this.stat(userId, itemFullPath);
          return {
            name: fileName,
            path: itemFullPath,
            isDirectory: stats.isDirectory(),
            size: stats.size,
            mtime: stats.mtime
          };
        } catch (e) {
          console.warn(`Gagal membaca stat untuk ${itemFullPath}`);
          return null;
        }
      });
      
    const results = await Promise.all(statsPromises);
    return results.filter(r => r !== null);
  }

  async mkdir(userId: string, fullPath: string): Promise<void> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.mkdir(internalPath, (err: any) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  async rename(userId: string, oldFullPath: string, newFullPath: string): Promise<void> {
    const oldParsed = this.parsePath(oldFullPath);
    const newParsed = this.parsePath(newFullPath);
    
    if (oldParsed.shareName !== newParsed.shareName) {
      throw new Error("Tidak bisa memindahkan file antar-Share secara langsung.");
    }
    
    const client = await this.getClient(userId, oldParsed.shareName);
    return new Promise((resolve, reject) => {
      client.rename(oldParsed.internalPath, newParsed.internalPath, (err: any) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  async unlink(userId: string, fullPath: string): Promise<void> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.unlink(internalPath, (err: any) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  async rmdir(userId: string, fullPath: string): Promise<void> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.rmdir(internalPath, (err: any) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  async createReadStream(userId: string, fullPath: string): Promise<any> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.createReadStream(internalPath, (err: any, readStream: any) => {
        if (err) reject(err);
        else resolve(readStream);
      });
    });
  }

  async createWriteStream(userId: string, fullPath: string): Promise<any> {
    const { shareName, internalPath } = this.parsePath(fullPath);
    const client = await this.getClient(userId, shareName);
    return new Promise((resolve, reject) => {
      client.createWriteStream(internalPath, (err: any, writeStream: any) => {
        if (err) reject(err);
        else resolve(writeStream);
      });
    });
  }

  // Untuk test connection, kita tes ke IPC$ (hidden share) agar pasti ada dan tak butuh baca file,
  // ATAU kita tes koneksi ke salah satu share default (misal IT)
  async testConnection(username: string, password: string): Promise<boolean> {
    let domain = this.domain;
    let actualUsername = username;

    if (username.includes('\\')) {
      const parts = username.split('\\');
      domain = parts[0];
      actualUsername = parts[1];
    }

    // Kita tes koneksi ke IPC$ yang merupakan share publik default windows
    const client = new SMB2({
      share: `\\\\${this.serverIp}\\IPC$`,
      domain: domain,
      username: actualUsername,
      password,
    });
    
    return new Promise((resolve) => {
      // Hanya butuh tree_connect success
      // IPC$ menolak readdir, jadi kita abaikan hasil readdir selama tidak error autentikasi
      client.readdir('', (err: any) => {
        client.disconnect();
        // Kalau error karena bukan directory (STATUS_INVALID_PARAMETER), berarti auth SUKSES!
        if (err && err.code === 'STATUS_INVALID_PARAMETER') {
           resolve(true);
        } else if (err && err.code === 'STATUS_LOGON_FAILURE') {
           console.error(`[SMB Login Failed for ${domain}\\${actualUsername}]`);
           resolve(false);
        } else if (err) {
           console.error(`[SMB Unknown Error]:`, err);
           // Anggap sukses kalau bukan logon failure
           resolve(true); 
        } else {
           resolve(true);
        }
      });
    });
  }

  // Menguji apakah user berhak masuk ke sebuah Share tertentu secara spesifik
  async testShareAccess(userId: string, shareName: string): Promise<boolean> {
    try {
      const client = await this.getClient(userId, shareName);
      return new Promise((resolve) => {
        client.readdir('', (err: any) => {
          if (err && err.code === 'STATUS_ACCESS_DENIED') {
            resolve(false);
          } else if (err && err.code === 'STATUS_INVALID_PARAMETER') {
            resolve(false); // share name format is bad
          } else if (err && err.code === 'STATUS_INVALID_INFO_CLASS') {
            resolve(false); // Bukan sebuah direktori normal (misal: share printer)
          } else {
            resolve(true);
          }
        });
      });
    } catch (e) {
      return false;
    }
  }
}
