use crate::{
    database::{Database, Error, Result},
    entities::{Billable, Client, Company, DateTime, Invoice, Job, NaiveDate, Tag, Worker},
    jobber::*,
};

impl JobberCreate for Database {
    fn create_company<T>(&mut self, f: impl FnOnce(&mut Company) -> Result<T>) -> Result<T> {
        let mut company = self.company.clone().unwrap_or_default();
        let result = f(&mut company)?;
        self.company = Some(company);
        Ok(result)
    }

    fn create_worker<T>(
        &mut self,
        id: &str,
        f: impl FnOnce(&mut Worker) -> Result<T>,
    ) -> Result<T> {
        let mut worker = self.workers.get(id).cloned().unwrap_or_default();
        let result = f(&mut worker)?;
        if self.workers.contains_key(id) {
            return Err(Error::WorkerExists(id.to_string()));
        }
        self.workers.insert(id.to_string(), worker);
        Ok(result)
    }

    fn create_client<T>(
        &mut self,
        id: &str,
        f: impl FnOnce(&mut Client) -> Result<T>,
    ) -> Result<T> {
        let mut client = self.clients.get(id).cloned().unwrap_or_default();
        if let Some(company) = &self.company {
            client.scheme = company.scheme.clone();
        }
        let result = f(&mut client)?;
        if self.clients.contains_key(id) {
            return Err(Error::ClientExists(id.to_string()));
        }
        self.clients.insert(id.to_string(), client);
        Ok(result)
    }

    fn create_job<T>(
        &mut self,
        pos: Option<usize>,
        f: impl FnOnce(&mut Job) -> Result<T>,
    ) -> Result<T> {
        let mut job = if let Some(pos) = pos {
            self.jobs.get(pos).cloned().unwrap_or_default()
        } else {
            Job::default()
        };
        let result = f(&mut job)?;
        if !self.workers.contains_key(&job.worker) {
            return Err(Error::UnknownWorker(job.worker));
        }
        if !self.clients.contains_key(&job.client) {
            return Err(Error::UnknownClient(job.client));
        }
        self.jobs.push(job);
        Ok(result)
    }

    fn create_tag<T>(&mut self, name: &str, f: impl FnOnce(&mut Tag) -> Result<T>) -> Result<T> {
        let mut tag = self.tags.get(name).cloned().unwrap_or_default();
        let result = f(&mut tag)?;
        if self.tags.contains_key(name) {
            return Err(Error::TagExists(name.to_string()));
        }
        self.tags.insert(name.to_string(), tag);
        Ok(result)
    }

    fn create_invoice(
        &mut self,
        job_ids: impl Iterator<Item = usize>,
        subject: Option<String>,
        until: Option<NaiveDate>,
    ) -> Result<usize> {
        let until = until.map(DateTime::from_naive_date);
        let mut bills: Vec<_> = Vec::new();
        let mut client = None;
        let mut currency = None;
        for job_id in job_ids {
            let job = &mut self.jobs[job_id];
            if let Some(c) = client
                && c != job.client
            {
                todo!("error different clients")
            } else {
                client = Some(job.client.clone())
            }

            if let Some(p) = &job.scheme(job_id)?.rate {
                let c1 = p.amount.currency();
                if let Some(c0) = &currency
                    && c0 != &c1
                {
                    todo!("error different currencies")
                } else {
                    currency = Some(c1);
                }
            }

            bills.push(job.bill(job_id, until)?)
        }

        if let Some(client) = client {
            self.invoices.push(Invoice {
                client,
                subject,
                bills,
                date: DateTime::now(),
                deleted: false,
            });
        }

        Ok(self.invoices.len() - 1)
    }
}
