use std::ops::Deref;

use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, quote};
use syn::parse::{Parse, ParseStream};
use syn::{
    ExprClosure, GenericArgument, Pat, PatType, Path, PathArguments, ReturnType, Token, Type,
    TypePath, TypeTuple, Visibility,
};

#[derive(Debug)]
pub struct DerivedMap {
    vis: Visibility,
    ident: Ident,
    parse_item_closure: ExprClosure,
    write_item_closure: Option<ExprClosure>,
    map_type: Option<Type>,
}

pub fn expand(options: DerivedMap) -> syn::Result<TokenStream> {
    let DerivedMap {
        vis,
        ident,
        parse_item_closure,
        write_item_closure,
        map_type,
    } = options;

    let source_item_type = if let Some(Pat::Type(pat_type)) = parse_item_closure.inputs.first() {
        &pat_type.ty
    } else if let Some(ReturnType::Type(_, return_type)) =
        write_item_closure.as_ref().map(|c| &c.output)
    {
        return_type
    } else {
        return Err(syn::Error::new_spanned(
            &parse_item_closure.inputs,
            "cannot determine source item type from parse or write closures",
        ));
    };

    let mut is_request_result = false;
    let mut parse_return_type = None;

    if let ReturnType::Type(_, return_type) = &parse_item_closure.output {
        if let Type::Path(TypePath {
            path: Path { segments, .. },
            ..
        }) = &**return_type
            && let Some(syn::PathSegment {
                ident,
                arguments: PathArguments::AngleBracketed(args),
            }) = segments.first()
            && let Some(GenericArgument::Type(ty)) = args.args.first()
            && ident == "RequestResult"
        {
            is_request_result = true;
            parse_return_type = Some(ty.clone());
        } else if parse_return_type.is_none() {
            parse_return_type = Some(return_type.deref().clone());
        }
    }
    let parse_return_type = if let Some(parse_return_type) = parse_return_type {
        parse_return_type
    } else if let Some(Pat::Type(PatType { ty, .. })) =
        write_item_closure.as_ref().and_then(|c| c.inputs.first())
    {
        ty.deref().clone()
    } else {
        return Err(syn::Error::new_spanned(
            &parse_item_closure.output,
            "cannot determine parse return type from parse or write closures",
        ));
    };
    let (key_type, value_type) = if let Type::Tuple(TypeTuple { ref elems, .. }) = parse_return_type
    {
        if elems.len() != 2 {
            return Err(syn::Error::new_spanned(
                &parse_return_type,
                "expected key-value tuple type",
            ));
        }
        (&elems[0], &elems[1])
    } else {
        return Err(syn::Error::new_spanned(
            &parse_return_type,
            "expected key-value tuple type",
        ));
    };

    let map_type = map_type.map_or_else(
        || quote! { ::std::collections::BTreeMap },
        darling::ToTokens::into_token_stream,
    );

    let parse_body = &parse_item_closure.body;
    let parse_expr = if is_request_result {
        quote! {
            { #parse_body }.map_err(|err: RequestError| err)?
        }
    } else {
        quote! {
            #parse_body
        }
    };

    let write_fn = write_item_closure.as_ref().map_or_else(
        || quote!(),
        |write_item_closure| {
            let write_body = &write_item_closure.body;
            quote! {
                pub fn write<I>(values: I) -> Vec<#source_item_type>
                where
                    I: ::std::iter::IntoIterator<Item = (#key_type, #value_type)>,
                {
                    values
                        .into_iter()
                        .map(|item| {
                            #write_body
                        })
                        .collect()
                }
            }
        },
    );

    Ok(quote! {
        #vis mod #ident {
            use super::*;

            pub fn parse<I>(values: I) -> RequestResult<#map_type<#key_type, #value_type>>
            where
                I: ::std::iter::IntoIterator<Item = #source_item_type>,
            {
                let mut m = #map_type::new();
                for item in values.into_iter() {
                    let (k, v): (#key_type, #value_type) = {
                        #parse_expr
                    };
                    if m.insert(k, v).is_some() {
                        return Err(CommonError::DuplicateValue.into());
                    }
                }
                Ok(m)
            }

            #write_fn
        }
    })
}

impl Parse for DerivedMap {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let vis = input.parse()?;
        let ident: Ident = input.parse()?;
        let _: Token![,] = input.parse()?;

        let map_type: Option<Type> = input.parse().ok();

        let _: Option<Token![,]> = input.parse()?;
        let parse_item_closure: ExprClosure = input.parse()?;
        let _: Option<Token![,]> = input.parse()?;
        let write_item_closure: Option<ExprClosure> = input.parse().ok();

        // Trailing comma
        let _: Option<Token![,]> = input.parse()?;

        Ok(Self {
            vis,
            ident,
            parse_item_closure,
            write_item_closure,
            map_type,
        })
    }
}
