use crate::error::{Error, Result};
use crate::{event::Event, nip05::Nip05Name};
use nauthz_grpc::authorization_client::AuthorizationClient;
use nauthz_grpc::event::TagEntry;
use nauthz_grpc::{Decision, Event as GrpcEvent, EventReply, EventRequest};
use tracing::{info, warn};

pub mod nauthz_grpc {
    tonic::include_proto!("nauthz");
}

// A decision for the DB to act upon
pub trait AuthzDecision: Send + Sync {
    fn permitted(&self) -> bool;
    fn message(&self) -> Option<String>;
}

impl AuthzDecision for EventReply {
    fn permitted(&self) -> bool {
        self.decision == Decision::Permit as i32
    }
    fn message(&self) -> Option<String> {
        self.message.clone()
    }
}

// A connection to an event admission GRPC server
pub struct EventAuthzService {
    server_addr: String,
    conn: Option<AuthorizationClient<tonic::transport::Channel>>,
}

// conversion of Nip05Names into GRPC type
impl std::convert::From<Nip05Name> for nauthz_grpc::event_request::Nip05Name {
    fn from(value: Nip05Name) -> Self {
        nauthz_grpc::event_request::Nip05Name {
            local: value.local.clone(),
            domain: value.domain,
        }
    }
}

// conversion of event tags into gprc struct
fn tags_to_protobuf(tags: &[Vec<String>]) -> Vec<TagEntry> {
    tags.iter()
        .map(|x| TagEntry { values: x.clone() })
        .collect()
}

impl EventAuthzService {
    pub async fn connect(server_addr: &str) -> EventAuthzService {
        let mut eas = EventAuthzService {
            server_addr: server_addr.to_string(),
            conn: None,
        };
        eas.ready_connection().await;
        eas
    }

    pub async fn ready_connection(&mut self) {
        if self.conn.is_none() {
            let client = AuthorizationClient::connect(self.server_addr.to_string()).await;
            if let Err(ref msg) = client {
                warn!("could not connect to nostr authz GRPC server: {:?}", msg);
            } else {
                info!("connected to nostr authorization GRPC server");
            }
            self.conn = client.ok();
        }
    }

    pub async fn admit_event(
        &mut self,
        event: &Event,
        ip: &str,
        origin: Option<String>,
        user_agent: Option<String>,
        nip05: Option<Nip05Name>,
        auth_pubkey: Option<Vec<u8>>,
    ) -> Result<Box<dyn AuthzDecision>> {
        self.ready_connection().await;
        let id_blob = hex::decode(&event.id)?;
        let pubkey_blob = hex::decode(&event.pubkey)?;
        let sig_blob = hex::decode(&event.sig)?;
        if let Some(ref mut c) = self.conn {
            let gevent = GrpcEvent {
                id: id_blob,
                pubkey: pubkey_blob,
                sig: sig_blob,
                created_at: event.created_at,
                kind: event.kind,
                content: event.content.clone(),
                tags: tags_to_protobuf(&event.tags),
            };
            let svr_res = c
                .event_admit(EventRequest {
                    event: Some(gevent),
                    ip_addr: Some(ip.to_string()),
                    origin,
                    user_agent,
                    auth_pubkey,
                    nip05: nip05.map(nauthz_grpc::event_request::Nip05Name::from),
                })
                .await?;
            let reply = svr_res.into_inner();
            Ok(Box::new(reply))
        } else {
            Err(Error::AuthzError)
        }
    }
}