const db = require('../db/models');
const ProjectsDBApi = require('../db/api/projects');
const SongsDBApi = require('../db/api/songs');
const GenerationRequestsDBApi = require('../db/api/generation_requests');
const RecordingSessionsDBApi = require('../db/api/recording_sessions');
const TracksDBApi = require('../db/api/tracks');
const ArrangementSectionsDBApi = require('../db/api/arrangement_sections');
const MixSessionsDBApi = require('../db/api/mix_sessions');
const MasteringSessionsDBApi = require('../db/api/mastering_sessions');
const ExportsDBApi = require('../db/api/exports');
const SongMetadataDBApi = require('../db/api/song_metadata');
const CoverArtworksDBApi = require('../db/api/cover_artworks');
const AssetsDBApi = require('../db/api/assets');

const { Op } = db.Sequelize;

const moods = ['happy', 'sad', 'spiritual', 'energetic', 'romantic', 'chill', 'aggressive'];
const vocalModes = ['instrumental', 'upload', 'record'];
const exportFormats = ['wav', 'mp3', 'stems_zip'];

const arrangementBlueprint = [
  { section_type: 'intro', label: 'Atmospheric Intro', start_bar: 1, bar_length: 8, order_index: 1 },
  { section_type: 'verse', label: 'Verse Groove', start_bar: 9, bar_length: 16, order_index: 2 },
  { section_type: 'chorus', label: 'Main Hook', start_bar: 25, bar_length: 16, order_index: 3 },
  { section_type: 'bridge', label: 'Lift & Transition', start_bar: 41, bar_length: 8, order_index: 4 },
  { section_type: 'outro', label: 'DJ Friendly Outro', start_bar: 49, bar_length: 8, order_index: 5 },
];

function badRequest(message) {
  const error = new Error(message);
  error.code = 400;
  return error;
}

function getOrganizationId(currentUser) {
  return currentUser?.organizationsId || currentUser?.organizations?.id || null;
}

function getArtistDisplay(currentUser) {
  const name = [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ').trim();
  if (name) {
    return name;
  }

  return currentUser?.email || 'Independent Artist';
}

function buildTrackBlueprint(genreName, vocalMode) {
  const name = `${genreName || ''}`.toLowerCase();

  let blueprint;

  if (name.includes('amapiano') || name.includes('lekompo') || name.includes('bolobedu')) {
    blueprint = [
      { name: 'Groove Drums', track_type: 'drums', instrument: 'traditional_drums', volume_db: -4 },
      { name: 'Log Drum Bass', track_type: 'bass', instrument: 'log_drum', volume_db: -3 },
      { name: 'Soul Chords', track_type: 'instrument', instrument: 'piano', volume_db: -5 },
      { name: 'Texture Synth', track_type: 'instrument', instrument: 'pads', volume_db: -8 },
    ];
  } else if (name.includes('gospel') || name.includes('jazz')) {
    blueprint = [
      { name: 'Drum Pocket', track_type: 'drums', instrument: 'perc', volume_db: -5 },
      { name: 'Piano Lead', track_type: 'instrument', instrument: 'piano', volume_db: -4 },
      { name: 'Warm Bass', track_type: 'bass', instrument: 'other', volume_db: -6 },
      { name: 'Choir Pad', track_type: 'instrument', instrument: 'strings', volume_db: -9 },
    ];
  } else if (name.includes('maskandi')) {
    blueprint = [
      { name: 'Maskandi Guitar', track_type: 'instrument', instrument: 'guitar', volume_db: -4 },
      { name: 'Rhythm Drums', track_type: 'drums', instrument: 'traditional_drums', volume_db: -6 },
      { name: 'Supporting Bass', track_type: 'bass', instrument: 'other', volume_db: -6 },
    ];
  } else {
    blueprint = [
      { name: 'Main Drums', track_type: 'drums', instrument: 'perc', volume_db: -5 },
      { name: 'Bass Foundation', track_type: 'bass', instrument: 'other', volume_db: -5 },
      { name: 'Lead Synth', track_type: 'instrument', instrument: 'synth', volume_db: -6 },
    ];
  }

  if (vocalMode !== 'instrumental') {
    blueprint.unshift({ name: 'Lead Vocal', track_type: 'vocal', instrument: 'voice', volume_db: -3 });
  }

  blueprint.push({ name: 'Master Bus', track_type: 'master', instrument: 'other', volume_db: -1 });

  return blueprint;
}

function buildDescription({ genreName, languageName, promptText, vocalMode, targetLabel }) {
  return [
    `${genreName} studio workflow`,
    languageName ? `for ${languageName} vocals` : null,
    vocalMode !== 'instrumental' ? `with ${vocalMode === 'upload' ? 'uploaded' : 'planned live'} vocal capture` : 'as an instrumental-first draft',
    targetLabel ? `aimed at ${targetLabel.toLowerCase().replace(/_/g, ' ')}` : null,
    promptText ? `Seed prompt: ${promptText}` : null,
  ]
    .filter(Boolean)
    .join('. ');
}

function sanitizeUploadedAudioFile(rawFile) {
  if (!rawFile || typeof rawFile !== 'object') {
    return null;
  }

  const name = `${rawFile.name || ''}`.trim();
  const privateUrl = `${rawFile.privateUrl || ''}`.trim();
  const publicUrl = `${rawFile.publicUrl || ''}`.trim();

  if (!name || !privateUrl || !publicUrl || !rawFile.new) {
    return null;
  }

  if (!/\.(mp3|wav)$/i.test(name) && !/\.(mp3|wav)$/i.test(privateUrl)) {
    throw badRequest('Only MP3 and WAV vocal uploads are supported right now.');
  }

  const sizeInBytes = Number(rawFile.sizeInBytes);

  return {
    id: rawFile.id || undefined,
    name,
    sizeInBytes: Number.isFinite(sizeInBytes) && sizeInBytes > 0 ? sizeInBytes : null,
    privateUrl,
    publicUrl,
    new: true,
  };
}

function generateIsrc() {
  const year = new Date().getFullYear().toString().slice(-2);
  const random = Math.random().toString().slice(2, 7);
  return `ZA-AIM-${year}-${random}`;
}

function mapSessionSummary(project, song, generationRequest, mixSession, masteringSession, exportJob, recordingSession, songMetadata, coverArtwork, vocalAsset, vocalUpload) {
  return {
    project: {
      id: project.id,
      name: project.name,
      status: project.status,
      href: `/projects/${project.id}`,
    },
    song: {
      id: song.id,
      title: song.title,
      bpm: song.bpm,
      key_signature: song.key_signature,
      mood: song.mood,
      href: `/songs/${song.id}`,
    },
    generationRequest: {
      id: generationRequest.id,
      status: generationRequest.status,
      request_type: generationRequest.request_type,
      href: `/generation_requests/${generationRequest.id}`,
    },
    recordingSession: recordingSession
      ? {
          id: recordingSession.id,
          status: recordingSession.status,
          href: `/recording_sessions/${recordingSession.id}`,
        }
      : null,
    vocalAsset: vocalAsset
      ? {
          id: vocalAsset.id,
          name: vocalAsset.name,
          fileName: vocalUpload?.name || vocalAsset.name,
          publicUrl: vocalUpload?.publicUrl || null,
          href: `/assets/${vocalAsset.id}`,
        }
      : null,
    mixSession: {
      id: mixSession.id,
      status: mixSession.status,
      href: `/mix_sessions/${mixSession.id}`,
    },
    masteringSession: {
      id: masteringSession.id,
      status: masteringSession.status,
      href: `/mastering_sessions/${masteringSession.id}`,
    },
    exportJob: {
      id: exportJob.id,
      status: exportJob.status,
      format: exportJob.format,
      href: `/exports/${exportJob.id}`,
    },
    songMetadata: {
      id: songMetadata.id,
      distribution_status: songMetadata.distribution_status,
      isrc: songMetadata.isrc,
      href: `/song_metadata/${songMetadata.id}`,
    },
    coverArtwork: {
      id: coverArtwork.id,
      status: coverArtwork.status,
      href: `/cover_artworks/${coverArtwork.id}`,
    },
  };
}

module.exports = class StudioService {
  static async getLaunchpad(currentUser) {
    const organizationId = getOrganizationId(currentUser);
    const scopedWhere = organizationId ? { organizationsId: organizationId } : {};

    const [genres, languages, masteringPresets, aiModels, recentProjects] = await Promise.all([
      db.genres.findAll({
        attributes: ['id', 'name', 'description', 'typical_bpm_min', 'typical_bpm_max'],
        where: {
          ...scopedWhere,
          origin: 'south_africa',
        },
        order: [['name', 'ASC']],
        limit: 16,
      }),
      db.languages.findAll({
        attributes: ['id', 'name', 'iso_code', 'is_supported'],
        where: {
          ...scopedWhere,
          is_supported: true,
        },
        order: [['name', 'ASC']],
        limit: 12,
      }),
      db.mastering_presets.findAll({
        attributes: ['id', 'name', 'target', 'lufs_target', 'true_peak_db'],
        where: scopedWhere,
        order: [['is_default', 'DESC'], ['name', 'ASC']],
        limit: 8,
      }),
      db.ai_models.findAll({
        attributes: ['id', 'name', 'model_type', 'provider', 'version'],
        where: {
          ...scopedWhere,
          is_active: true,
          model_type: {
            [Op.in]: ['music_generation', 'mixing', 'mastering', 'metadata'],
          },
        },
        order: [['model_type', 'ASC'], ['name', 'ASC']],
        limit: 8,
      }),
      db.projects.findAll({
        attributes: ['id', 'name', 'status', 'target_bpm', 'target_key', 'mood', 'updatedAt'],
        where: {
          ...scopedWhere,
          createdById: currentUser.id,
        },
        include: [
          {
            model: db.genres,
            as: 'primary_genre',
            attributes: ['id', 'name'],
          },
        ],
        order: [['updatedAt', 'DESC']],
        limit: 6,
      }),
    ]);

    const recentSessions = await Promise.all(
      recentProjects.map(async (project) => {
        const song = await db.songs.findOne({
          attributes: ['id', 'title', 'bpm', 'key_signature', 'mood', 'createdAt'],
          where: {
            ...scopedWhere,
            projectId: project.id,
          },
          order: [['createdAt', 'DESC']],
        });

        const [generationRequest, mixSession, masteringSession, exportJob] = song
          ? await Promise.all([
              db.generation_requests.findOne({
                attributes: ['id', 'status', 'request_type', 'createdAt'],
                where: {
                  ...scopedWhere,
                  songId: song.id,
                },
                order: [['createdAt', 'DESC']],
              }),
              db.mix_sessions.findOne({
                attributes: ['id', 'status', 'mix_type', 'createdAt'],
                where: {
                  ...scopedWhere,
                  songId: song.id,
                },
                order: [['createdAt', 'DESC']],
              }),
              db.mastering_sessions.findOne({
                attributes: ['id', 'status', 'createdAt'],
                where: {
                  ...scopedWhere,
                  songId: song.id,
                },
                order: [['createdAt', 'DESC']],
              }),
              db.exports.findOne({
                attributes: ['id', 'status', 'format', 'createdAt'],
                where: {
                  ...scopedWhere,
                  songId: song.id,
                },
                order: [['createdAt', 'DESC']],
              }),
            ])
          : [null, null, null, null];

        return {
          id: project.id,
          name: project.name,
          status: project.status,
          mood: project.mood,
          target_bpm: project.target_bpm,
          target_key: project.target_key,
          updatedAt: project.updatedAt,
          genre: project.primary_genre ? { id: project.primary_genre.id, name: project.primary_genre.name } : null,
          projectHref: `/projects/${project.id}`,
          song: song
            ? {
                id: song.id,
                title: song.title,
                href: `/songs/${song.id}`,
              }
            : null,
          generationRequest: generationRequest
            ? {
                id: generationRequest.id,
                status: generationRequest.status,
                request_type: generationRequest.request_type,
                href: `/generation_requests/${generationRequest.id}`,
              }
            : null,
          mixSession: mixSession
            ? {
                id: mixSession.id,
                status: mixSession.status,
                href: `/mix_sessions/${mixSession.id}`,
              }
            : null,
          masteringSession: masteringSession
            ? {
                id: masteringSession.id,
                status: masteringSession.status,
                href: `/mastering_sessions/${masteringSession.id}`,
              }
            : null,
          exportJob: exportJob
            ? {
                id: exportJob.id,
                status: exportJob.status,
                format: exportJob.format,
                href: `/exports/${exportJob.id}`,
              }
            : null,
        };
      }),
    );

    return {
      genres: genres.map((genre) => genre.toJSON()),
      languages: languages.map((language) => language.toJSON()),
      masteringPresets: masteringPresets.map((preset) => preset.toJSON()),
      aiModels: aiModels.map((model) => model.toJSON()),
      recentSessions,
    };
  }

  static async createLaunchpadSession(data, currentUser) {
    const title = `${data.title || ''}`.trim();
    const promptText = `${data.promptText || ''}`.trim();
    const organizationId = getOrganizationId(currentUser);

    if (!title || title.length < 3) {
      throw badRequest('Please enter a project title with at least 3 characters.');
    }

    if (!data.genreId) {
      throw badRequest('Please choose a South African genre to shape the session.');
    }

    if (!data.languageId) {
      throw badRequest('Please choose the main vocal language for this session.');
    }

    if (!promptText || promptText.length < 12) {
      throw badRequest('Please provide a more descriptive beat prompt so the session has direction.');
    }

    if (!moods.includes(data.mood)) {
      throw badRequest('Please choose a valid mood.');
    }

    if (!vocalModes.includes(data.vocalMode)) {
      throw badRequest('Please choose a valid vocal workflow.');
    }

    if (!exportFormats.includes(data.exportFormat)) {
      throw badRequest('Please choose a valid export format.');
    }

    const targetBpm = data.targetBpm ? Number(data.targetBpm) : null;
    if (targetBpm && (Number.isNaN(targetBpm) || targetBpm < 60 || targetBpm > 180)) {
      throw badRequest('Target BPM must be between 60 and 180.');
    }

    const uploadedVocal = sanitizeUploadedAudioFile(data.vocalUpload);

    if (data.vocalMode === 'upload' && !uploadedVocal) {
      throw badRequest('Upload an MP3 or WAV vocal take before launching the session.');
    }

    const scopedWhere = organizationId ? { organizationsId: organizationId } : {};
    const [genre, language, selectedPreset, musicModel] = await Promise.all([
      db.genres.findOne({ where: { id: data.genreId, ...scopedWhere } }),
      db.languages.findOne({ where: { id: data.languageId, ...scopedWhere } }),
      data.masteringPresetId
        ? db.mastering_presets.findOne({ where: { id: data.masteringPresetId, ...scopedWhere } })
        : db.mastering_presets.findOne({
            where: scopedWhere,
            order: [['is_default', 'DESC'], ['name', 'ASC']],
          }),
      db.ai_models.findOne({
        where: {
          ...scopedWhere,
          is_active: true,
          model_type: 'music_generation',
        },
        order: [['name', 'ASC']],
      }),
    ]);

    if (!genre) {
      throw badRequest('The selected genre could not be found.');
    }

    if (!language) {
      throw badRequest('The selected language could not be found.');
    }

    const projectStatus = data.vocalMode === 'instrumental' ? 'in_progress' : 'ready_for_mix';
    const now = new Date();
    const transaction = await db.sequelize.transaction();

    try {
      const project = await ProjectsDBApi.create(
        {
          name: title,
          description: buildDescription({
            genreName: genre.name,
            languageName: language.name,
            promptText,
            vocalMode: data.vocalMode,
            targetLabel: selectedPreset?.target,
          }),
          status: projectStatus,
          target_bpm: targetBpm || genre.typical_bpm_min || null,
          target_key: data.targetKey || null,
          mood: data.mood,
          target_duration_seconds: 240,
          collaboration_enabled: false,
          last_opened_at: now,
          created_user: currentUser.id,
          primary_genre: genre.id,
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const song = await SongsDBApi.create(
        {
          project: project.id,
          title,
          artist_display: getArtistDisplay(currentUser),
          genre: genre.id,
          bpm: targetBpm || genre.typical_bpm_min || null,
          key_signature: data.targetKey || null,
          mood: data.mood,
          duration_seconds: 240,
          vocal_language: language.id,
          explicit_content: false,
          lyrics: data.notes ? `${promptText}\n\nCreative notes:\n${data.notes}` : promptText,
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const vocalAsset = uploadedVocal
        ? await AssetsDBApi.create(
            {
              asset_type: 'audio',
              audio_role: 'vocal_raw',
              name: uploadedVocal.name,
              uploaded_user: currentUser.id,
              project: project.id,
              song: song.id,
              is_stereo: false,
              organizations: organizationId,
              file_blobs: [uploadedVocal],
            },
            { currentUser, transaction },
          )
        : null;

      const generationRequest = await GenerationRequestsDBApi.create(
        {
          project: project.id,
          song: song.id,
          requested_user: currentUser.id,
          model: musicModel?.id || null,
          input_asset: vocalAsset?.id || null,
          request_type: data.vocalMode === 'upload' ? 'generate_beat_from_vocals' : 'generate_beat_from_text',
          prompt_text: promptText,
          target_genre: genre.id,
          target_bpm: song.bpm,
          target_key: song.key_signature,
          mood: data.mood,
          status: 'queued',
          queued_at: now,
          estimated_cost: data.vocalMode === 'upload' ? 2.8 : 1.6,
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const recordingSession =
        data.vocalMode === 'instrumental'
          ? null
          : await RecordingSessionsDBApi.create(
              {
                project: project.id,
                song: song.id,
                started_user: currentUser.id,
                status: 'planned',
                input_channels: 1,
                sample_rate_hz: 48000,
                organizations: organizationId,
              },
              { currentUser, transaction },
            );

      const tracks = await Promise.all(
        buildTrackBlueprint(genre.name, data.vocalMode).map((track, index) =>
          TracksDBApi.create(
            {
              song: song.id,
              name: track.name,
              track_type: track.track_type,
              instrument: track.instrument,
              volume_db: track.volume_db,
              pan: index % 2 === 0 ? -5 : 5,
              source_asset: track.track_type === 'vocal' && vocalAsset ? vocalAsset.id : null,
              organizations: organizationId,
            },
            { currentUser, transaction },
          ),
        ),
      );

      const arrangementSections = await Promise.all(
        arrangementBlueprint.map((section) =>
          ArrangementSectionsDBApi.create(
            {
              song: song.id,
              section_type: section.section_type,
              label: section.label,
              start_bar: section.start_bar,
              bar_length: section.bar_length,
              order_index: section.order_index,
              organizations: organizationId,
            },
            { currentUser, transaction },
          ),
        ),
      );

      const mixSession = await MixSessionsDBApi.create(
        {
          song: song.id,
          requested_user: currentUser.id,
          mix_type: 'auto_mix',
          status: 'queued',
          stereo_widening_amount: genre.name.toLowerCase().includes('gospel') ? 0.4 : 0.6,
          compression_amount: 0.45,
          eq_amount: 0.5,
          started_at: null,
          finished_at: null,
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const masteringSession = await MasteringSessionsDBApi.create(
        {
          song: song.id,
          requested_user: currentUser.id,
          preset: selectedPreset?.id || null,
          status: 'queued',
          started_at: null,
          finished_at: null,
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const exportJob = await ExportsDBApi.create(
        {
          song: song.id,
          requested_user: currentUser.id,
          format: data.exportFormat,
          quality: data.exportFormat === 'wav' ? 'studio' : 'standard',
          include_stems: Boolean(data.includeStems) || data.exportFormat === 'stems_zip',
          status: 'queued',
          requested_at: now,
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const songMetadata = await SongMetadataDBApi.create(
        {
          song: song.id,
          isrc: generateIsrc(),
          release_title: title,
          release_date: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
          label_name: 'Independent Release',
          publisher: getArtistDisplay(currentUser),
          copyright_notice: `© ${new Date().getFullYear()} ${getArtistDisplay(currentUser)}`,
          distribution_status: 'ready',
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      const coverArtwork = await CoverArtworksDBApi.create(
        {
          song: song.id,
          prompt: `${genre.name} cover artwork, South African nightlife palette, ${data.mood} energy, premium music release design`,
          status: 'generated',
          organizations: organizationId,
        },
        { currentUser, transaction },
      );

      await transaction.commit();

      return {
        message: `${title} is now staged for beat generation, vocal prep, mix, mastering, and export.`,
        session: {
          ...mapSessionSummary(
            project,
            song,
            generationRequest,
            mixSession,
            masteringSession,
            exportJob,
            recordingSession,
            songMetadata,
            coverArtwork,
            vocalAsset,
            uploadedVocal,
          ),
          arrangementSections: arrangementSections.map((section) => ({
            id: section.id,
            label: section.label,
            section_type: section.section_type,
            start_bar: section.start_bar,
            bar_length: section.bar_length,
          })),
          tracks: tracks.map((track) => ({
            id: track.id,
            name: track.name,
            track_type: track.track_type,
            instrument: track.instrument,
          })),
        },
      };
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }
};
