import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Build } from '../../entities/build.entity';
import { User } from '../../entities/user.entity';
import { PlatformSettings } from '../../entities/platform-settings.entity';
import { GoogleDriveService } from '../drive/google-drive.service';

export interface InstallInfo {
  platform: 'android' | 'ios';
  installUrl: string;
  appName?: string;
  version?: string;
  buildNumber?: string;
  size?: number;
  publishedAt?: string;
  shortCode?: string;
  /** Release notes / update description */
  updateDescription?: string;
  /** App icon URL (from extracted build icon) */
  iconUrl?: string;
  /** True when the file was removed from Drive or is no longer accessible */
  unavailable?: boolean;
  message?: string;
  /** Build type: For iOS (Development/AdHoc/Enterprise/AppStore), For Android (Debug/Release) */
  buildType?: string;
  /** iOS only: Provisioning profile type */
  provisioningProfile?: string;
  /** iOS only: UDIDs included in the build */
  udids?: string[];
  /** Whether this build is password protected */
  isPasswordProtected?: boolean;
  /** Uploader name (if enabled in settings) */
  uploaderName?: string;
}

@Injectable()
export class InstallService {
  constructor(
    @InjectRepository(Build)
    private readonly buildRepo: Repository<Build>,
    @InjectRepository(User)
    private readonly userRepo: Repository<User>,
    @InjectRepository(PlatformSettings)
    private readonly settingsRepo: Repository<PlatformSettings>,
    private readonly driveService: GoogleDriveService,
  ) { }

  async getPublicSettings(userId?: string): Promise<{ buildLinkExpiryDays: number; aiReleaseNotesEnabled: boolean }> {
    const settings = await this.settingsRepo.findOne({ where: { id: 'global' } });
    let aiEnabled = settings?.aiReleaseNotesEnabled ?? true;

    if (userId) {
      const user = await this.userRepo.findOne({ where: { id: userId }, select: ['id', 'aiReleaseNotesEnabled'] });
      if (user?.aiReleaseNotesEnabled !== null && user?.aiReleaseNotesEnabled !== undefined) {
        aiEnabled = user.aiReleaseNotesEnabled;
      }
    }

    return {
      buildLinkExpiryDays: settings?.buildLinkExpiryDays ?? 0,
      aiReleaseNotesEnabled: aiEnabled,
    };
  }

  private async isBuildExpired(build: Build, owner?: User): Promise<boolean> {
    const userExpiry = owner?.buildLinkExpiryDays;
    let expiryDays: number;
    if (userExpiry !== null && userExpiry !== undefined) {
      expiryDays = userExpiry;
    } else {
      const settings = await this.settingsRepo.findOne({ where: { id: 'global' } });
      expiryDays = settings?.buildLinkExpiryDays ?? 0;
    }
    if (expiryDays <= 0) return false;
    const createdAt = build.createdAt instanceof Date ? build.createdAt : new Date(build.createdAt);
    const expiryDate = new Date(createdAt.getTime() + expiryDays * 24 * 60 * 60 * 1000);
    return new Date() > expiryDate;
  }

  async getInstallInfo(buildId: string): Promise<InstallInfo | null> {
    const build = await this.buildRepo.findOne({
      where: { id: buildId },
      relations: ['app', 'app.user'],
    });
    if (!build) return null;

    const owner = build.app.user;
    const isLocal = process.env.NODE_ENV !== 'production' && (!process.env.FRONTEND_URL || process.env.FRONTEND_URL.includes('localhost'));
    const defaultApiUrl = isLocal ? 'http://localhost:4000' : 'https://deployhub-back.workzy.co';
    let apiUrl = (process.env.PUBLIC_BASE_URL || defaultApiUrl).trim();
    if (apiUrl.endsWith('/')) apiUrl = apiUrl.slice(0, -1);

    // Auto-correct if frontend URL is mistakenly provided instead of backend URL
    if (apiUrl.includes('deployhub.workzy.co') && !apiUrl.includes('deployhub-back.workzy.co')) {
      apiUrl = 'https://deployhub-back.workzy.co';
    }

    const base = {
      appName: build.app.appName,
      version: build.version,
      buildNumber: build.buildNumber,
      size: Number(build.size) || 0,
      publishedAt: build.createdAt instanceof Date ? build.createdAt.toISOString() : String(build.createdAt),
      shortCode: build.shortCode ?? undefined,
      updateDescription: build.updateDescription ?? undefined,
      iconUrl: (build.app.iconData || build.app.iconFileId)
        ? `${apiUrl}/api/drive/icon/${build.app.id}`
        : undefined,
      buildType: build.buildType ?? undefined,
      provisioningProfile: build.provisioningProfile ?? undefined,
      udids: build.udids ?? undefined,
      isPasswordProtected: build.isPasswordProtected || false,
      uploaderName: owner?.showUploaderInfo ? owner.name : undefined,
    };

    if (await this.isBuildExpired(build, owner)) {
      return {
        platform: build.app.platform as 'android' | 'ios',
        installUrl: '',
        ...base,
        unavailable: true,
        message: 'This build link has expired.',
      };
    }

    let available: boolean;
    try {
      available = await this.driveService.isFileAvailable(build.driveFileId, owner ?? undefined);
    } catch (err) {
      console.warn(`[getInstallInfo] isFileAvailable threw for build ${buildId}, assuming available:`, err instanceof Error ? err.message : err);
      // All DeployHub files have anyone:reader permission; don't block public
      // access just because the owner's credentials are invalid.
      available = true;
    }
    if (!available) {
      return {
        platform: build.app.platform as 'android' | 'ios',
        installUrl: '',
        ...base,
        unavailable: true,
        message: 'This build has been removed or the link has expired.',
      };
    }

    if (build.app.platform === 'android') {
      const installUrl = `/api/install/download/${build.id}`;
      return { platform: 'android', installUrl, ...base };
    }
    if (build.app.platform === 'ios') {
      if (!build.plistFileId) {
        return {
          platform: 'ios',
          installUrl: '',
          ...base,
          unavailable: true,
          message: 'This build is missing its manifest and cannot be installed.',
        };
      }
      const plistAvailable = await this.driveService.isFileAvailable(build.plistFileId, owner ?? undefined);
      if (!plistAvailable) {
        return {
          platform: 'ios',
          installUrl: '',
          ...base,
          unavailable: true,
          message: 'This build has been removed or the link has expired.',
        };
      }
      const plistUrl = this.driveService.getPublicFileUrl(build.plistFileId);
      const installUrl = `itms-services://?action=download-manifest&url=${encodeURIComponent(plistUrl)}`;
      return { platform: 'ios', installUrl, ...base };
    }
    return null;
  }

  async getApkStream(
    buildId: string,
  ): Promise<{
    stream: import('stream').Readable;
    mimeType: string;
    fileName: string;
    size?: number;
  } | null> {
    const build = await this.buildRepo.findOne({
      where: { id: buildId },
      relations: ['app'],
    });
    if (!build) {
      console.error(`[getApkStream] Build not found: ${buildId}`);
      return null;
    }
    if (build.app.platform !== 'android') {
      console.error(`[getApkStream] Build ${buildId} is not android (platform: ${build.app.platform})`);
      return null;
    }

    const owner = await this.userRepo.findOne({ where: { id: build.app.userId } });

    const appName = build.app.appName.replace(/[^a-zA-Z0-9._-]/g, '_');
    const fileName = `${appName}-v${build.version}.apk`;

    // Try streaming with owner's OAuth credentials (skip isFileAvailable — just
    // attempt the stream directly; if the file is gone the API will tell us)
    if (owner) {
      try {
        const result = await this.driveService.streamFile(owner, build.driveFileId);
        return {
          stream: result.stream,
          mimeType: 'application/vnd.android.package-archive',
          fileName,
          size: result.size,
        };
      } catch (err) {
        console.warn(`[getApkStream] OAuth stream failed for build ${buildId}, falling back to public download: ${err instanceof Error ? err.message : err}`);
      }
    }

    // Fallback: stream via public URL (files have anyone:reader permission)
    try {
      const result = await this.driveService.streamFilePublic(build.driveFileId);
      return {
        stream: result.stream,
        mimeType: 'application/vnd.android.package-archive',
        fileName,
        size: result.size,
      };
    } catch (err) {
      console.error(`[getApkStream] Public streaming also failed for build ${buildId}: ${err instanceof Error ? err.message : err}`);
      return null;
    }
  }

  async getIpaStream(
    buildId: string,
  ): Promise<{
    stream: import('stream').Readable;
    mimeType: string;
    fileName: string;
    size?: number;
  } | null> {
    const build = await this.buildRepo.findOne({
      where: { id: buildId },
      relations: ['app'],
    });
    if (!build || build.app.platform !== 'ios') return null;

    const owner = await this.userRepo.findOne({ where: { id: build.app.userId } });

    const appName = build.app.appName.replace(/[^a-zA-Z0-9._-]/g, '_');
    const fileName = `${appName}-v${build.version}.ipa`;

    // Try streaming with owner's OAuth credentials directly
    if (owner) {
      try {
        const result = await this.driveService.streamFile(owner, build.driveFileId);
        return {
          stream: result.stream,
          mimeType: 'application/octet-stream',
          fileName,
          size: result.size,
        };
      } catch (err) {
        console.warn(`[getIpaStream] OAuth stream failed for build ${buildId}, falling back to public download: ${err instanceof Error ? err.message : err}`);
      }
    }

    // Fallback: stream via public URL (files have anyone:reader permission)
    try {
      const result = await this.driveService.streamFilePublic(build.driveFileId);
      return {
        stream: result.stream,
        mimeType: 'application/octet-stream',
        fileName,
        size: result.size,
      };
    } catch (err) {
      console.error(`[getIpaStream] Public streaming also failed for build ${buildId}: ${err instanceof Error ? err.message : err}`);
      return null;
    }
  }

  /**
   * Get the Google Drive direct download URL for a build.
   * Used as the ultimate redirect fallback when server-side streaming fails.
   */
  async getPublicDownloadUrl(buildId: string): Promise<string | null> {
    const build = await this.buildRepo.findOne({
      where: { id: buildId },
      select: ['id', 'driveFileId'],
    });
    if (!build?.driveFileId) return null;
    return this.driveService.getPublicFileUrl(build.driveFileId);
  }

  async getBuildForRedirect(buildId: string): Promise<{ url: string } | null> {
    const info = await this.getInstallInfo(buildId);
    if (!info) return null;
    return { url: info.installUrl };
  }

  async getBuildIdByShortCode(code: string): Promise<string | null> {
    console.log('getBuildIdByShortCode', code);
    if (!code || code.length > 16) return null;
    const build = await this.buildRepo.findOne({
      where: { shortCode: code.toUpperCase() },
      select: ['id'],
    });
    console.log('build', build);
    return build?.id ?? null;
  }
}
