'use server';

import { z } from 'zod';
import { getDbClient } from '../dbClient';
import { Bill, BillAttachment, BillingLocation, YearMonth } from '../db-types';
import { ObjectId } from 'mongodb';
import { withUser } from '@/app/lib/auth';
import { AuthenticatedUser } from '../types/next-auth';
import { gotoHome } from './navigationActions';

export type State = {
    errors?: {
        billName?: string[];
        billAttachment?: string[],
        billNotes?: string[],
        payedAmount?: string[],
    };
    message?:string | null;
}

const FormSchema = z.object({
    _id: z.string(),
    billName: z.coerce.string().min(1, "Bill Name is required."),
    billNotes: z.string(),
    payedAmount: z.string().nullable().transform((val, ctx) => {

        if(!val || val === '') {
            return null;
        }

        const parsed = parseFloat(val.replace(',', '.'));

        if (isNaN(parsed)) {
            ctx.addIssue({
                code: z.ZodIssueCode.custom,
                message: "Not a number",
            });
        
            // This is a special symbol you can use to
            // return early from the transform function.
            // It has type `never` so it does not affect the
            // inferred return type.
            return z.NEVER;
        }

        if (parsed < 0) {
            ctx.addIssue({
                code: z.ZodIssueCode.custom,
                message: "Value must be a positive number",
            });
        
            // This is a special symbol you can use to
            // return early from the transform function.
            // It has type `never` so it does not affect the
            // inferred return type.
            return z.NEVER;
        }
  
        return Math.floor(parsed * 100); // value is stored in cents

      }),
  });

  parseFloat

const UpdateBill = FormSchema.omit({ _id: true });

/**
 * converts the file to a format stored in the database
 * @param billAttachment 
 * @returns 
 */
const serializeAttachment = async (billAttachment: File | null) => {

    if (!billAttachment) {
        return null;
    }

    const {
        name: fileName,
        size: fileSize,
        type: fileType,
        lastModified: fileLastModified,
    } = billAttachment;

    if(!fileName || fileName === 'undefined') {
        return null;
    }

    // convert the billAttachment file contents to format that can be stored in the database
    const fileContents = await billAttachment.arrayBuffer();
    const fileContentsBase64 = Buffer.from(fileContents).toString('base64');

    // create an object to store the file in the database
    return({
        fileName,
        fileSize,
        fileType,
        fileLastModified,
        fileContentsBase64,
    } as BillAttachment);
}

/**
 * Server-side action which adds or updates a bill
 * @param locationId location of the bill
 * @param billId ID of the bill
 * @param prevState previous state of the form
 * @param formData form data
 * @returns 
 */
export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationId: string, billId:string|undefined, billYear:number|undefined, billMonth:number|undefined, prevState:State, formData: FormData) => {

    const { id: userId } = user;

    const validatedFields = UpdateBill.safeParse({
        billName: formData.get('billName'),
        billNotes: formData.get('billNotes'),
        payedAmount: formData.get('payedAmount'),
    });

    // If form validation fails, return errors early. Otherwise, continue...
    if(!validatedFields.success) {
        console.log("updateBill.validation-error");
        return({
            errors: validatedFields.error.flatten().fieldErrors,
            message: "Missing Fields. Field to Update Bill.",
        });
    }

    const {
        billName,
        billNotes,
        payedAmount,
    } = validatedFields.data;

    const billPaid = formData.get('billPaid') === 'on';
    const barcodeImage = formData.get('barcodeImage')?.valueOf() as string;

    // update the bill in the mongodb
    const dbClient = await getDbClient();
    
    const billAttachment = await serializeAttachment(formData.get('billAttachment') as File);

    if(billId) {

        // if there is an attachment, update the attachment field
        // otherwise, do not update the attachment field
        const mongoDbSet = billAttachment ? {
            "bills.$[elem].name": billName,
            "bills.$[elem].paid": billPaid,
            "bills.$[elem].attachment": billAttachment,
            "bills.$[elem].notes": billNotes,
            "bills.$[elem].payedAmount": payedAmount,
            "bills.$[elem].barcodeImage": barcodeImage,
            
        }: {
            "bills.$[elem].name": billName,
            "bills.$[elem].paid": billPaid,
            "bills.$[elem].notes": billNotes,
            "bills.$[elem].payedAmount": payedAmount,
            "bills.$[elem].barcodeImage": barcodeImage,
        };

        // find a location with the given locationID
        const post = await dbClient.collection<BillingLocation>("lokacije").updateOne(
            {
                _id: locationId, // find a location with the given locationID
                userId // make sure that the location belongs to the user
            },
            {
                $set: mongoDbSet
            }, {
                arrayFilters: [
                    { "elem._id": { $eq: billId } } // find a bill with the given billID
                ]
            });
    } else {
        // find a location with the given locationID
        const post = await dbClient.collection<BillingLocation>("lokacije").updateOne(
            {
                _id: locationId, // find a location with the given locationID
                userId // make sure that the location belongs to the user
            },
            {
                $push: {
                    bills: {
                        _id: (new ObjectId()).toHexString(),
                        name: billName,
                        paid: billPaid,
                        attachment: billAttachment,
                        notes: billNotes,
                        payedAmount,
                        barcodeImage,
                    }
                }
            });
    }
    if(billYear && billMonth    ) {
        await gotoHome({ year: billYear, month: billMonth });
    }
})

export const fetchBillById = withUser(async (user:AuthenticatedUser, locationID:string, billID:string, includeAttachmentBinary:boolean = false) => {

    const { id: userId } = user;

    const dbClient = await getDbClient();

    // don't include the attachment binary data in the response
    // if the attachment binary data is not needed
    const projection = includeAttachmentBinary ? {} : {
        "bills.attachment.fileContentsBase64": 0,
    };

    // find a location with the given locationID
    const billLocation = await dbClient.collection<BillingLocation>("lokacije").findOne(
        {
            _id: locationID,
            userId
        },
        {
            projection
        })

    if(!billLocation) {
        console.log(`Location ${locationID} not found`);
        return(null);
    }

    // find a bill with the given billID
    const bill = billLocation?.bills.find(({ _id }) => _id.toString() === billID);

    if(!bill) {
        console.log('Bill not found');
        return(null);
    }

    return([billLocation, bill] as [BillingLocation, Bill]);
})

export const deleteBillById = withUser(async (user:AuthenticatedUser, locationID:string, billID:string, year:number, month:number) => {

    const { id: userId } = user;

    const dbClient = await getDbClient();

    // find a location with the given locationID
    const post = await dbClient.collection<BillingLocation>("lokacije").updateOne(
        {
            _id: locationID, // find a location with the given locationID
            userId // make sure that the location belongs to the user
        },
        {
            // remove the bill with the given billID
            $pull: {
                bills: {
                    _id: billID
                }
            }
        });
    
    await gotoHome({year, month});
    return(post.modifiedCount);
});