use super::*;

pub(super) async fn blockhash(
  Extension(index): Extension<Arc<Index>>,
) -> ServerResult<Json<String>> {
  task::block_in_place(|| {
    Ok(Json(
      index
        .block_hash(None)?
        .ok_or_not_found(|| "blockhash")?
        .to_string(),
    ))
  })
}

pub(super) async fn blockhash_at_height(
  Extension(index): Extension<Arc<Index>>,
  Path(height): Path<u32>,
) -> ServerResult<Json<String>> {
  task::block_in_place(|| {
    Ok(Json(
      index
        .block_hash(Some(height))?
        .ok_or_not_found(|| "blockhash")?
        .to_string(),
    ))
  })
}

pub(super) async fn block_hash_from_height_string(
  Extension(index): Extension<Arc<Index>>,
  Path(height): Path<u32>,
) -> ServerResult<String> {
  task::block_in_place(|| {
    Ok(
      index
        .block_hash(Some(height))?
        .ok_or_not_found(|| "blockhash")?
        .to_string(),
    )
  })
}

pub(super) async fn blockhash_string(
  Extension(index): Extension<Arc<Index>>,
) -> ServerResult<String> {
  task::block_in_place(|| {
    Ok(
      index
        .block_hash(None)?
        .ok_or_not_found(|| "blockhash")?
        .to_string(),
    )
  })
}

pub(super) async fn blockheight_string(
  Extension(index): Extension<Arc<Index>>,
) -> ServerResult<String> {
  task::block_in_place(|| {
    Ok(
      index
        .block_height()?
        .ok_or_not_found(|| "blockheight")?
        .to_string(),
    )
  })
}

pub(super) async fn blockinfo(
  Extension(index): Extension<Arc<Index>>,
  Path(DeserializeFromStr(query)): Path<DeserializeFromStr<query::Block>>,
) -> ServerResult<Json<api::BlockInfo>> {
  task::block_in_place(|| {
    let hash = match query {
      query::Block::Hash(hash) => hash,
      query::Block::Height(height) => index
        .block_hash(Some(height))?
        .ok_or_not_found(|| format!("block {height}"))?,
    };

    let header = index
      .block_header(hash)?
      .ok_or_not_found(|| format!("block {hash}"))?;

    let info = index
      .block_header_info(hash)?
      .ok_or_not_found(|| format!("block {hash}"))?;

    let stats = index
      .block_stats(info.height.try_into().unwrap())?
      .ok_or_not_found(|| format!("block {hash}"))?;

    Ok(Json(api::BlockInfo {
      average_fee: stats.avg_fee.to_sat(),
      average_fee_rate: stats.avg_fee_rate.to_sat(),
      bits: header.bits.to_consensus(),
      chainwork: info.chainwork.try_into().unwrap(),
      confirmations: info.confirmations,
      difficulty: info.difficulty,
      hash,
      feerate_percentiles: [
        stats.fee_rate_percentiles.fr_10th.to_sat(),
        stats.fee_rate_percentiles.fr_25th.to_sat(),
        stats.fee_rate_percentiles.fr_50th.to_sat(),
        stats.fee_rate_percentiles.fr_75th.to_sat(),
        stats.fee_rate_percentiles.fr_90th.to_sat(),
      ],
      height: info.height.try_into().unwrap(),
      max_fee: stats.max_fee.to_sat(),
      max_fee_rate: stats.max_fee_rate.to_sat(),
      max_tx_size: stats.max_tx_size,
      median_fee: stats.median_fee.to_sat(),
      median_time: info
        .median_time
        .map(|median_time| median_time.try_into().unwrap()),
      merkle_root: info.merkle_root,
      min_fee: stats.min_fee.to_sat(),
      min_fee_rate: stats.min_fee_rate.to_sat(),
      next_block: info.next_block_hash,
      nonce: info.nonce,
      previous_block: info.previous_block_hash,
      subsidy: stats.subsidy.to_sat(),
      target: target_as_block_hash(header.target()),
      timestamp: info.time.try_into().unwrap(),
      total_fee: stats.total_fee.to_sat(),
      total_size: stats.total_size,
      total_weight: stats.total_weight,
      transaction_count: info.n_tx.try_into().unwrap(),
      #[allow(clippy::cast_sign_loss)]
      version: info.version.to_consensus() as u32,
    }))
  })
}

pub(super) async fn blocktime_string(
  Extension(index): Extension<Arc<Index>>,
) -> ServerResult<String> {
  task::block_in_place(|| {
    Ok(
      index
        .block_time(index.block_height()?.ok_or_not_found(|| "blocktime")?)?
        .unix_timestamp()
        .to_string(),
    )
  })
}

pub(super) async fn children(
  Extension(index): Extension<Arc<Index>>,
  Path(inscription_id): Path<InscriptionId>,
) -> ServerResult {
  children_paginated(Extension(index), Path((inscription_id, 0))).await
}

pub(super) async fn children_inscriptions(
  Extension(index): Extension<Arc<Index>>,
  Path(inscription_id): Path<InscriptionId>,
) -> ServerResult {
  children_inscriptions_paginated(Extension(index), Path((inscription_id, 0))).await
}

pub(super) async fn children_inscriptions_paginated(
  Extension(index): Extension<Arc<Index>>,
  Path((parent, page)): Path<(InscriptionId, usize)>,
) -> ServerResult {
  task::block_in_place(|| {
    let parent_sequence_number = index
      .get_inscription_entry(parent)?
      .ok_or_not_found(|| format!("inscription {parent}"))?
      .sequence_number;

    let (ids, more) =
      index.get_children_by_sequence_number_paginated(parent_sequence_number, 100, page)?;

    let children = ids
      .into_iter()
      .map(|inscription_id| get_relative_inscription(&index, inscription_id))
      .collect::<ServerResult<Vec<api::RelativeInscriptionRecursive>>>()?;

    Ok(
      Json(api::ChildInscriptions {
        children,
        more,
        page,
      })
      .into_response(),
    )
  })
}

pub(super) async fn children_paginated(
  Extension(index): Extension<Arc<Index>>,
  Path((parent, page)): Path<(InscriptionId, usize)>,
) -> ServerResult {
  task::block_in_place(|| {
    let Some(parent) = index.get_inscription_entry(parent)? else {
      return Err(ServerError::NotFound(format!(
        "inscription {parent} not found"
      )));
    };

    let parent_sequence_number = parent.sequence_number;

    let (ids, more) =
      index.get_children_by_sequence_number_paginated(parent_sequence_number, 100, page)?;

    Ok(Json(api::Children { ids, more, page }).into_response())
  })
}

pub(super) async fn content(
  index: Extension<Arc<Index>>,
  settings: Extension<Arc<Settings>>,
  server_config: Extension<Arc<ServerConfig>>,
  Path(inscription_id): Path<InscriptionId>,
  accept_encoding: AcceptEncoding,
) -> ServerResult {
  content_inner(
    &index,
    &settings,
    &server_config,
    inscription_id,
    accept_encoding,
    true,
  )
  .await
}

pub(super) async fn content_inner(
  index: &Index,
  settings: &Settings,
  server_config: &ServerConfig,
  inscription_id: InscriptionId,
  accept_encoding: AcceptEncoding,
  cache: bool,
) -> ServerResult {
  task::block_in_place(|| {
    if settings.is_hidden(inscription_id) {
      return Ok(PreviewUnknownHtml.into_response());
    }

    let Some(mut inscription) = index.get_inscription_by_id(inscription_id)? else {
      return Err(ServerError::NotFound(format!(
        "inscription {inscription_id} not found"
      )));
    };

    if let Some(delegate) = inscription.delegate() {
      inscription = index
        .get_inscription_by_id(delegate)?
        .ok_or_not_found(|| format!("delegate {inscription_id}"))?
    }

    Ok(
      content_response(inscription, accept_encoding, server_config, cache)?
        .ok_or_not_found(|| format!("inscription {inscription_id} content"))?
        .into_response(),
    )
  })
}

pub(super) fn content_response(
  inscription: Inscription,
  accept_encoding: AcceptEncoding,
  server_config: &ServerConfig,
  cache: bool,
) -> ServerResult<Option<(HeaderMap, Vec<u8>)>> {
  let mut headers = HeaderMap::new();

  match &server_config.csp_origin {
    None => {
      headers.insert(
        header::CONTENT_SECURITY_POLICY,
        HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:"),
      );
      headers.append(
          header::CONTENT_SECURITY_POLICY,
          HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime *:*/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"),
        );
    }
    Some(origin) => {
      let csp = format!(
        "default-src {origin}/content/ {origin}/blockheight {origin}/blockhash {origin}/blockhash/ {origin}/blocktime {origin}/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"
      );
      headers.insert(
        header::CONTENT_SECURITY_POLICY,
        HeaderValue::from_str(&csp).map_err(|err| ServerError::Internal(Error::from(err)))?,
      );
    }
  }

  headers.insert(
    header::CACHE_CONTROL,
    HeaderValue::from_static(if cache {
      "public, max-age=1209600, immutable"
    } else {
      "no-store"
    }),
  );

  headers.insert(
    header::CONTENT_TYPE,
    inscription
      .content_type()
      .and_then(|content_type| content_type.parse().ok())
      .unwrap_or(HeaderValue::from_static("application/octet-stream")),
  );

  if let Some(content_encoding) = inscription.content_encoding() {
    if accept_encoding.is_acceptable(&content_encoding) {
      headers.insert(header::CONTENT_ENCODING, content_encoding);
    } else if server_config.decompress && content_encoding == BROTLI {
      let Some(body) = inscription.into_body() else {
        return Ok(None);
      };

      let mut decompressed = Vec::new();

      brotli::Decompressor::new(body.as_slice(), BROTLI_BUFFER_SIZE)
        .read_to_end(&mut decompressed)
        .map_err(|err| ServerError::Internal(err.into()))?;

      return Ok(Some((headers, decompressed)));
    } else {
      return Err(ServerError::NotAcceptable {
        accept_encoding,
        content_encoding,
      });
    }
  }

  let Some(body) = inscription.into_body() else {
    return Ok(None);
  };

  Ok(Some((headers, body)))
}

pub(super) async fn inscription(
  Extension(index): Extension<Arc<Index>>,
  Extension(server_config): Extension<Arc<ServerConfig>>,
  Path(inscription_id): Path<InscriptionId>,
) -> ServerResult {
  task::block_in_place(|| {
    let Some(inscription) = index.get_inscription_by_id(inscription_id)? else {
      return Err(ServerError::NotFound(format!(
        "inscription {inscription_id} not found",
      )));
    };

    let entry = index
      .get_inscription_entry(inscription_id)
      .unwrap()
      .unwrap();

    let satpoint = index
      .get_inscription_satpoint_by_id(inscription_id)
      .ok()
      .flatten()
      .unwrap();

    let output = if satpoint.outpoint == unbound_outpoint() {
      None
    } else {
      Some(
        index
          .get_transaction(satpoint.outpoint.txid)?
          .ok_or_not_found(|| format!("inscription {inscription_id} current transaction"))?
          .output
          .into_iter()
          .nth(satpoint.outpoint.vout.try_into().unwrap())
          .ok_or_not_found(|| format!("inscription {inscription_id} current transaction output"))?,
      )
    };

    let address = output.as_ref().and_then(|output| {
      server_config
        .chain
        .address_from_script(&output.script_pubkey)
        .ok()
        .map(|address| address.to_string())
    });

    Ok(
      Json(api::InscriptionRecursive {
        charms: Charm::charms(entry.charms),
        content_type: inscription.content_type().map(|s| s.to_string()),
        content_length: inscription.content_length(),
        delegate: inscription.delegate(),
        fee: entry.fee,
        height: entry.height,
        id: inscription_id,
        number: entry.inscription_number,
        output: satpoint.outpoint,
        value: output.as_ref().map(|o| o.value.to_sat()),
        sat: entry.sat,
        satpoint,
        timestamp: timestamp(entry.timestamp.into()).timestamp(),
        address,
      })
      .into_response(),
    )
  })
}

pub(super) async fn metadata(
  Extension(index): Extension<Arc<Index>>,
  Path(inscription_id): Path<InscriptionId>,
) -> ServerResult {
  task::block_in_place(|| {
    let Some(inscription) = index.get_inscription_by_id(inscription_id)? else {
      return Err(ServerError::NotFound(format!(
        "inscription {inscription_id} not found"
      )));
    };

    let metadata = inscription
      .metadata
      .ok_or_not_found(|| format!("inscription {inscription_id} metadata"))?;

    Ok(Json(hex::encode(metadata)).into_response())
  })
}

pub(super) async fn parents(
  Extension(index): Extension<Arc<Index>>,
  Path(inscription_id): Path<InscriptionId>,
) -> ServerResult {
  parents_paginated(Extension(index), Path((inscription_id, 0))).await
}

pub async fn parent_inscriptions(
  Extension(index): Extension<Arc<Index>>,
  Path(inscription_id): Path<InscriptionId>,
) -> ServerResult {
  parent_inscriptions_paginated(Extension(index), Path((inscription_id, 0))).await
}

pub async fn parent_inscriptions_paginated(
  Extension(index): Extension<Arc<Index>>,
  Path((child, page)): Path<(InscriptionId, usize)>,
) -> ServerResult {
  task::block_in_place(|| {
    let entry = index
      .get_inscription_entry(child)?
      .ok_or_not_found(|| format!("inscription {child}"))?;

    let (ids, more) = index.get_parents_by_sequence_number_paginated(entry.parents, 100, page)?;

    let parents = ids
      .into_iter()
      .map(|inscription_id| get_relative_inscription(&index, inscription_id))
      .collect::<ServerResult<Vec<api::RelativeInscriptionRecursive>>>()?;

    Ok(
      Json(api::ParentInscriptions {
        parents,
        more,
        page,
      })
      .into_response(),
    )
  })
}

pub(super) async fn parents_paginated(
  Extension(index): Extension<Arc<Index>>,
  Path((inscription_id, page)): Path<(InscriptionId, usize)>,
) -> ServerResult {
  task::block_in_place(|| {
    let child = index
      .get_inscription_entry(inscription_id)?
      .ok_or_not_found(|| format!("inscription {inscription_id}"))?;

    let (ids, more) = index.get_parents_by_sequence_number_paginated(child.parents, 100, page)?;

    let page_index =
      u32::try_from(page).map_err(|_| anyhow!("page index {} out of range", page))?;

    Ok(
      Json(api::Inscriptions {
        ids,
        more,
        page_index,
      })
      .into_response(),
    )
  })
}

pub(super) async fn sat(
  Extension(index): Extension<Arc<Index>>,
  Path(sat): Path<u64>,
) -> ServerResult<Json<api::SatInscriptions>> {
  sat_paginated(Extension(index), Path((sat, 0))).await
}

pub(super) async fn sat_at_index(
  Extension(index): Extension<Arc<Index>>,
  Path((DeserializeFromStr(sat), inscription_index)): Path<(DeserializeFromStr<Sat>, isize)>,
) -> ServerResult<(HeaderMap, Json<api::SatInscription>)> {
  task::block_in_place(|| {
    if !index.has_sat_index() {
      return Err(ServerError::NotFound(
        "this server has no sat index".to_string(),
      ));
    }

    let id = index.get_inscription_id_by_sat_indexed(sat, inscription_index)?;

    let mut headers = HeaderMap::new();

    if inscription_index < 0 {
      headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
    }

    Ok((headers, Json(api::SatInscription { id })))
  })
}

pub(super) async fn sat_paginated(
  Extension(index): Extension<Arc<Index>>,
  Path((sat, page)): Path<(u64, u64)>,
) -> ServerResult<Json<api::SatInscriptions>> {
  task::block_in_place(|| {
    if !index.has_sat_index() {
      return Err(ServerError::NotFound("this server has no sat index".into()));
    }

    let (ids, more) = index.get_inscription_ids_by_sat_paginated(Sat(sat), 100, page)?;

    Ok(Json(api::SatInscriptions { ids, more, page }))
  })
}

pub(super) async fn sat_at_index_content(
  index: Extension<Arc<Index>>,
  settings: Extension<Arc<Settings>>,
  server_config: Extension<Arc<ServerConfig>>,
  Path((DeserializeFromStr(sat), inscription_index)): Path<(DeserializeFromStr<Sat>, isize)>,
  accept_encoding: AcceptEncoding,
) -> ServerResult {
  let inscription_id = task::block_in_place(|| {
    if !index.has_sat_index() {
      return Err(ServerError::NotFound("this server has no sat index".into()));
    }

    index
      .get_inscription_id_by_sat_indexed(sat, inscription_index)?
      .ok_or_not_found(|| format!("inscription on sat {sat}"))
  })?;

  content_inner(
    &index,
    &settings,
    &server_config,
    inscription_id,
    accept_encoding,
    inscription_index >= 0,
  )
  .await
}

fn get_relative_inscription(
  index: &Index,
  id: InscriptionId,
) -> ServerResult<api::RelativeInscriptionRecursive> {
  let entry = index
    .get_inscription_entry(id)?
    .ok_or_not_found(|| format!("inscription {id}"))?;

  let satpoint = index
    .get_inscription_satpoint_by_id(id)?
    .ok_or_not_found(|| format!("satpoint for inscription {id}"))?;

  Ok(api::RelativeInscriptionRecursive {
    charms: Charm::charms(entry.charms),
    fee: entry.fee,
    height: entry.height,
    id,
    number: entry.inscription_number,
    output: satpoint.outpoint,
    sat: entry.sat,
    satpoint,
    timestamp: timestamp(entry.timestamp.into()).timestamp(),
  })
}

pub(super) async fn tx(
  Extension(index): Extension<Arc<Index>>,
  Path(txid): Path<Txid>,
) -> ServerResult<Json<String>> {
  task::block_in_place(|| {
    Ok(Json(
      index
        .get_transaction_hex_recursive(txid)?
        .ok_or_not_found(|| format!("transaction {txid}"))?,
    ))
  })
}

pub(super) async fn undelegated_content(
  Extension(index): Extension<Arc<Index>>,
  Extension(settings): Extension<Arc<Settings>>,
  Extension(server_config): Extension<Arc<ServerConfig>>,
  Path(inscription_id): Path<InscriptionId>,
  accept_encoding: AcceptEncoding,
) -> ServerResult {
  task::block_in_place(|| {
    if settings.is_hidden(inscription_id) {
      return Ok(PreviewUnknownHtml.into_response());
    }

    let inscription = index
      .get_inscription_by_id(inscription_id)?
      .ok_or_not_found(|| format!("inscription {inscription_id}"))?;

    Ok(
      r::content_response(inscription, accept_encoding, &server_config, true)?
        .ok_or_not_found(|| format!("inscription {inscription_id} content"))?
        .into_response(),
    )
  })
}

pub(super) async fn utxo(
  Extension(index): Extension<Arc<Index>>,
  Path(outpoint): Path<OutPoint>,
) -> ServerResult {
  task::block_in_place(|| {
    Ok(
      Json(
        index
          .get_utxo_recursive(outpoint)?
          .ok_or_not_found(|| format!("output {outpoint}"))?,
      )
      .into_response(),
    )
  })
}
