use std::{net::SocketAddr, path::PathBuf, sync::Arc};

use cache::AppCache;
use db::database::AppDatabase;
use tonic::transport::Server;
use uuid::Uuid;

use crate::rpc::{
    archive::ArchiveServiceImpl,
    blame::BlameServiceImpl,
    blob::BlobServiceImpl,
    branch::BranchServiceImpl,
    commit::CommitServiceImpl,
    diff::DiffServiceImpl,
    fork::ForkServiceImpl,
    init::InitServiceImpl,
    merge::MergeServiceImpl,
    proto::{
        archive_service_server::ArchiveServiceServer,
        blame_service_server::BlameServiceServer,
        blob_service_server::BlobServiceServer,
        branch_service_server::BranchServiceServer,
        commit_service_server::CommitServiceServer,
        diff_service_server::DiffServiceServer,
        fork_service_server::ForkServiceServer,
        init_service_server::InitServiceServer,
        merge_service_server::MergeServiceServer,
        tag_service_server::TagServiceServer,
        tree_service_server::TreeServiceServer,
    },
    registry::{RepoRegistry, shared_registry},
    tag::TagServiceImpl,
    tree::TreeServiceImpl,
};
use crate::sync::ReceiveSyncService;

type RepoId = Uuid;

pub struct GitServer {
    addr: SocketAddr,
    pub registry: Arc<RepoRegistry>,
    pub cache: AppCache,
    pub sync: ReceiveSyncService,
}

impl GitServer {
    pub fn new(
        addr: SocketAddr,
        db: AppDatabase,
        cache: AppCache,
        sync: ReceiveSyncService,
    ) -> Self {
        Self {
            addr,
            cache: cache.clone(),
            registry: shared_registry(db, cache),
            sync,
        }
    }

    pub fn register_repo(&self, repo_id: Uuid, bare_dir: PathBuf) {
        self.registry.register(repo_id, bare_dir);
    }

    pub async fn serve(self) -> Result<(), Box<dyn std::error::Error>> {
        let archive = ArchiveServiceServer::new(ArchiveServiceImpl {
            registry: self.registry.clone(),
        });
        let blame = BlameServiceServer::new(BlameServiceImpl {
            registry: self.registry.clone(),
            cache: self.cache.clone(),
        });
        let blob = BlobServiceServer::new(BlobServiceImpl {
            registry: self.registry.clone(),
            cache: self.cache.clone(),
        });
        let branch = BranchServiceServer::new(BranchServiceImpl {
            registry: self.registry.clone(),
        });
        let commit = CommitServiceServer::new(CommitServiceImpl {
            registry: self.registry.clone(),
            cache: self.cache.clone(),
            sync: self.sync.clone(),
        });
        let diff = DiffServiceServer::new(DiffServiceImpl {
            registry: self.registry.clone(),
            cache: self.cache.clone(),
        });
        let fork = ForkServiceServer::new(ForkServiceImpl {
            registry: self.registry.clone(),
        });
        let init = InitServiceServer::new(InitServiceImpl {
            registry: self.registry.clone(),
            sync: self.sync.clone(),
        });
        let merge = MergeServiceServer::new(MergeServiceImpl {
            registry: self.registry.clone(),
        });
        let tag = TagServiceServer::new(TagServiceImpl {
            registry: self.registry.clone(),
        });
        let tree = TreeServiceServer::new(TreeServiceImpl {
            registry: self.registry.clone(),
            cache: self.cache.clone(),
        });

        Server::builder()
            .max_frame_size(Some(16 * 1024 * 1024 - 1))
            .add_service(archive)
            .add_service(blame)
            .add_service(blob)
            .add_service(branch)
            .add_service(commit)
            .add_service(diff)
            .add_service(fork)
            .add_service(init)
            .add_service(merge)
            .add_service(tag)
            .add_service(tree)
            .serve(self.addr)
            .await?;

        Ok(())
    }
}

pub struct GitServerBuilder {
    addr: SocketAddr,
    db: AppDatabase,
    cache: AppCache,
    sync: ReceiveSyncService,
    repos: Vec<(RepoId, PathBuf)>,
}

impl GitServerBuilder {
    pub fn new(
        addr: SocketAddr,
        db: AppDatabase,
        cache: AppCache,
        sync: ReceiveSyncService,
    ) -> Self {
        Self {
            addr,
            db,
            cache,
            sync,
            repos: Vec::new(),
        }
    }

    pub fn repo(
        mut self,
        repo_id: RepoId,
        bare_dir: impl Into<PathBuf>,
    ) -> Self {
        self.repos.push((repo_id, bare_dir.into()));
        self
    }

    pub fn build(self) -> GitServer {
        let server = GitServer::new(self.addr, self.db, self.cache, self.sync);
        for (repo_id, bare_dir) in self.repos {
            server.register_repo(repo_id, bare_dir);
        }
        server
    }
}
