<?php

namespace App\Http\Controllers;

use App\Models\ModelHasRoles;
use App\Models\OrganisationAllowance;
use Illuminate\Database\QueryException;
use App\Interfaces\UserRepositoryInterface;
use App\Interfaces\OrganisationRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;
use App\Mail\ForgetPasswordMail;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Spatie\Permission\Models\Role;


class AuthController extends Controller
{
    private UserRepositoryInterface $userRepository;
    private OrganisationRepositoryInterface $orgRepository;

    public function __construct(UserRepositoryInterface $userRepository, OrganisationRepositoryInterface $orgRepository)
    {
        $this->middleware('auth:api', ['except' => ['login', 'register', 'forgotPassword', 'resetPassword']]);
        $this->userRepository = $userRepository;
        $this->orgRepository = $orgRepository;
    }

    private function generateUniqueSlug($name, $orgId)
    {
        // Extract first 3 alphanumeric characters and convert to uppercase
        $slug = strtoupper(preg_replace('/[^a-zA-Z0-9]/', '', substr($name, 0, 3)));

        // Get existing slugs for the organization
        $existingSlugs = OrganisationAllowance::where('org_id', $orgId)
            ->where('slug', 'LIKE', $slug . '%')
            ->pluck('slug')
            ->toArray();

        // Find a unique numeric suffix (00-99)
        $count = 0;
        while (in_array("{$slug}" . str_pad($count, 2, '0', STR_PAD_LEFT), $existingSlugs)) {
            $count++;
            if ($count > 99) { // Avoid infinite loops
                throw new \Exception('Slug limit reached for this name.');
            }
        }

        return "{$slug}" . str_pad($count, 2, '0', STR_PAD_LEFT);
    }



    //Register Function
    public function register(Request $request)
    {
        try {
            // Define validation rules
            $validator = Validator::make($request->all(), [
                'name' => 'required|string',
                'company' => 'required|string',
                'email' => [
                    'required',
                    'string',
                    'email',
                    'max:255',
                    Rule::unique('users', 'email'), // Checks uniqueness in users table
                    Rule::unique('organisations', 'email'), // Checks uniqueness in organisations table
                ],
                'phone' => 'required|string|max:255|unique:organisations',
                'password' => 'required|string|min:8',
                'address' => 'string',
            ]);

            // Check if validation fails
            if ($validator->fails()) {
                return response()->json([
                    'status' => 'failed',
                    'message' => $validator->errors()->first(),
                ], 422);
            }

            // Use a DB transaction
            DB::beginTransaction();

            $name = explode(" ", $request->name);

            if (count($name) < 2) {
                return response()->json([
                    'status' => 'error',
                    'message' => 'Kindly enter your firstname and lastname!',
                ], 422);
            }

            // Create Organisation
            $organisation = $this->orgRepository->createNewOrg([
                'name' => $request->company,
                'email' => $request->email,
                'phone' => $request->phone,
                'address' => $request->address,
                'date_founded' => $request->date_founded,
                'monthly_payroll' => $request->monthly_payroll,
                'bi-weekly_payroll' => $request->bi_weekly_payroll,
                'special_payroll' => $request->special_payroll,
                'industry' => $request->industry,
                'logo' => $request->logo,
                'is_active' => true
            ]);

            // Create Default Org Pension Agreement
            $singlePensionData = [
                "type" => "single",
                "employee_contribution" => 0,
                "company_contribution" => 20,
                "org_id" => $organisation->id
            ];

            $sharedPensionData = [
                "type" => "shared",
                "employee_contribution" => 8,
                "company_contribution" => 10,
                "org_id" => $organisation->id
            ];

            // Create Default Tax Rules
            $taxData = [
                [
                    "salary_range" => 300000,
                    "percentage" => 7,
                    "org_id" => $organisation->id,
                ],
                [
                    "salary_range" => 400000,
                    "percentage" => 15,
                    "org_id" => $organisation->id,
                ],
                [
                    "salary_range" => 600000,
                    "percentage" => 19,
                    "org_id" => $organisation->id,
                ],
                [
                    "salary_range" => 100000,
                    "percentage" => 10,
                    "org_id" => $organisation->id,
                ],
            ];

            // Create Default Time Settings
            $timeData = [
                "resumption_time" => Carbon::parse('09:00')->format('Y-m-d H:i:s'),
                "closing_time" => Carbon::parse("17:00")->format('Y-m-d H:i:s'),
                "default_late_period" => 30,
                "name" => 'Default Shift',
                "org_id" => $organisation->id,
            ];

            // Find and register products for the organisation
            $products = $this->orgRepository->getPayratyProducts();
            foreach ($products as $product) {
                $orgProducts = [
                    "organisation_id" => $organisation->id,
                    "product_id" => $product->id,
                    "is_active" => false,
                ];
                $this->orgRepository->registerOrgProduct($orgProducts);
            }

            // Create Pension Agreements
            $this->orgRepository->createPensionAgreement($singlePensionData);
            $this->orgRepository->createPensionAgreement($sharedPensionData);

            // Create Time and Tax Settings
            $this->orgRepository->createTimeSettings($timeData);
            foreach ($taxData as $data) {
                $this->orgRepository->createTaxSettings($data);
            }

            // Create User and Profile
            $userData = [
                'email' => $request->email,
                'password' => Hash::make($request->password),
                'user_type' => 'admin',
                'is_registered' => true,
                'is_active' => true,
                'organisation_id' => $organisation->id,
                'type' => 'company',
                'role' => 'Admin',
            ];

            $user = $this->userRepository->createUser($userData);
            $first_name = $name[0];
            $last_name = $name[1];

            $this->userRepository->createUserProfile([
                'user_id' => $user->id,
                'first_name' => $first_name,
                'last_name' => $last_name,
            ]);

            $this->userRepository->createBankProfile([
                'user_id' => $user->id,
            ]);

            $this->userRepository->createJobProfile([
                'user_id' => $user->id,
            ]);

            // Create Allowances
            $transportSlug = $this->generateUniqueSlug("Transport Allowance", $organisation->id);
            $houseSlug = $this->generateUniqueSlug("House Allowance", $organisation->id);
            $allowanceData = [
                [
                    "org_id" => $organisation->id,
                    "type" => "percentage",
                    "name" => "Transport Allowance",
                    "description" => "This covers transport allowance",
                    "added_by" => $user->id,
                    "slug" => $transportSlug,
                    "is_default" => 1,
                    "status" => true,
                    "default_type" => "transport",
                ],
                [
                    "org_id" => $organisation->id,
                    "type" => "percentage",
                    "name" => "House Allowance",
                    "description" => "This covers housing allowance",
                    "added_by" => $user->id,
                    "slug" => $houseSlug,
                    "is_default" => 1,
                    "status" => true,
                    "default_type" => "housing",
                ],
            ];

            foreach ($allowanceData as $data) {
                $this->orgRepository->createOrgAllowance($data);
            }

            //Create Chart of accounts for accounting
            $chartAccountTypes = [
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Assets"
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Liabilities"
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Equity"
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Income"
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Costs of Goods Sold"
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Expenses"
                ]
            ];

            $chartTypeIds = [];

            foreach ($chartAccountTypes as $data) {
                $chartTypes = $this->orgRepository->createChartTypes($data);
                $chartTypeIds[$chartTypes->name] = $chartTypes->id;
            }

            //Create Chart sub types  of accounts for accounting
            $chartAccountSubTypes = [
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Current Asset",
                    "type" => $chartTypeIds['Assets'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Inventory Asset",
                    "type" => $chartTypeIds['Assets'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Non-current Asset",
                    "type" => $chartTypeIds['Assets'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Current Liabilities",
                    "type" => $chartTypeIds['Liabilities'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Long Term Liabilities",
                    "type" => $chartTypeIds['Liabilities'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Share Capital",
                    "type" => $chartTypeIds['Liabilities'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Retained Earnings",
                    "type" => $chartTypeIds['Liabilities'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Owners Equity",
                    "type" => $chartTypeIds['Equity'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Sales Revenue",
                    "type" => $chartTypeIds['Income'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Other Revenue",
                    "type" => $chartTypeIds['Income'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Costs of Goods Sold",
                    "type" => $chartTypeIds['Costs of Goods Sold'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "Payroll Expenses",
                    "type" => $chartTypeIds['Expenses'] ?? null
                ],
                [
                    "organisation_id" => $organisation->id,
                    "created_by" => $user->id,
                    "name" => "General and Administrative expenses",
                    "type" => $chartTypeIds['Expenses'] ?? null
                ],
            ];

            foreach ($chartAccountSubTypes as $data) {
                $this->orgRepository->createChartSubTypes($data);
            }

            //Create Default Warehouse 
            $wareHouseData = [
                "name" => "Defualt Warehouse",
                "email" => $organisation->email,
                "priority" => 1,
                "is_default" => 1,
                "status" => "active",
                "organisation_id" => $organisation->id,
            ];

            $this->orgRepository->createWareHouse($wareHouseData);
            // asign roles
            ModelHasRoles::firstOrCreate([
                'model_type' => 'App\Models\User',
                'model_id' => $user->id,
                'role_id' => 1,
                'organisation_id' => $organisation->id,
            ], ['role_id' => 1]);




            // Send Email
            $mailData = ["name" => $organisation->name];
            Mail::to($organisation->email)->send(new WelcomeEmail($mailData));

            // Create token
            $token = Auth::login($user);

            // Commit transaction
            DB::commit();

            return response()->json([
                'status' => 'success',
                'message' => 'Organisation successfully registered',
                'token' => $token,
                'data' => [
                    "new" => [
                        'user_id' => $user->id,
                        'first_name' => $first_name,
                        'last_name' => $last_name,
                    ],
                    'organisation' => $organisation,
                    'user' => $user
                ]
            ], 200);
        } catch (\Exception $e) {
            // Rollback the transaction on failure
            DB::rollBack();
            return response()->json([
                'status' => 'failed',
                'message' => $e->getMessage(),
            ], 500);
        }
    }



    //Login Function
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
        ]);
        $credentials = $request->only('email', 'password');

        $token = Auth::attempt($credentials);
        if (!$token) {
            return response()->json([
                'status' => 'failed',
                'message' => 'Email or Password Incorrect',
            ], 401);
        }

        $user = Auth::user();

        //Get User Organisation
        $organisation = $this->orgRepository->findById($user->organisation_id);

        if (!$organisation) {
            return response()->json([
                'status' => 'failed',
                'message' => 'Login unsuccessful, user doesn\'t belong to a company, Contact Admin',
            ], 400);
        }

        //Check if user can log in
        if ($user->is_active == false) {
            return response()->json([
                'status' => 'failed',
                'message' => 'Login unsuccessful, account deactivated, Contact Admin',
            ], 400);
        }

        if ($user->is_registered == false) {
            return response()->json([
                'status' => 'failed',
                'message' => 'Login unsuccessful, user account setup not completed, Contact Admin',
            ], 400);
        }

        // hris 
        $roles = ['company' => 74, 'hr' => 75, 'employee' => 76];
        $role_id = $roles[$user->type] ?? 76;
        if ($role_id) {
            $modelHris = ModelHasRoles::firstOrCreate(
                [
                    'model_type' => 'App\Models\User',
                    'model_id' => $user->id,

                    'organisation_id' => $organisation->id,
                    'role_id' => $role_id
                ]
            );
        }


        return response()->json([
            'status' => 'success',
            'message' => 'Login successful',
            'token' => $token,
            'modelHris' => $modelHris,
            'data' => [
                'organisation' => $organisation,
                'user' => $user,
                'is_demo_account' => env("DEMO_ACCOUNT") === $request->email ?? false
            ],
        ]);
    }

    //Login For Group Account 
    public function loginByGroup(Request $request)
    {
        //Find User Credentails
        $orgId = $request->orgId;
        $org = $this->orgRepository->findById($orgId);
        if (!$org) {
            return response()->json([
                'status' => 'failed',
                'message' => 'Organisation not found',
            ], 400);
        }

        $user = $this->userRepository->findByField("organisation_id", $orgId);
        if (!$user) {
            return response()->json([
                'status' => 'failed',
                'message' => 'No user found for this organisation',
            ], 404);
        }

        // Log in the user manually (bypass password check)
        Auth::login($user);

        // Generate a JWT token manually
        $token = Auth::fromUser($user);
        if (!$token) {
            return response()->json([
                'status' => 'failed',
                'message' => 'Cannot access organisation at this time',
            ], 401);
        }
        return response()->json([
            'status' => 'success',
            'message' => 'Login successful',
            'token' => $token,
            'data' => [
                'organisation' => $org,
                'user' => $user
            ],
        ]);
    }

    //Forget Password
    public function forgotPassword(Request $request)
    {
        try {
            $email = $request->email;
            $user = $this->userRepository->findByEmail($email);

            if (!$user) {
                return response()->json([
                    'status' => 'Failed',
                    'message' => 'Email Not Found',
                    'data' => []
                ], 500);
            }

            //Generate Reset Token
            $resetToken = Str::random(22);
            $futureTime = Carbon::now()->addHours(1);
            $timestamp = $futureTime->timestamp;

            $resetData = [
                "hashedToken" => $resetToken,
                "timestamp" => $timestamp
            ];
            //Update database with values
            $resetUser = $this->userRepository->updateUser($user->id, $resetData);

            //SEND MAIL
            $emailData = [
                "link" => env("FRONTEND_URL") . "/reset-password/{$resetToken}",
                "email" => $email,
                "expirationTime" => "One Hour"
            ];
            Mail::to($email)->send(new ForgetPasswordMail($emailData));
            return response()->json([
                'status' => 'success',
                'message' => 'Account Verified, Reset Token Sent',
                'dsta' => [],
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => 'failed',
                'message' => $e->getMessage(),
                'data' => []
            ], 500);
        }
    }

    //Reset Password
    public function resetPassword(Request $request)
    {
        try {
            $token = $request->token;
            $password = Hash::make($request->password);

            //Check If Token Matches
            $user = $this->userRepository->findByField('password_reset_token', $token);

            if (!$user) {
                return response()->json([
                    'status' => 'Failed',
                    'message' => 'Token Invalid',
                    'data' => []
                ], 500);
            }

            //Check For Time
            $timestamp = Carbon::now()->timestamp;

            if ($timestamp > $user->password_expires) {
                return response()->json([
                    'status' => 'failed',
                    'message' => 'Token Expired',
                    'data' => []
                ], 500);
            }

            //Reset Password if everything checks out.
            $resetData = [
                "password" => $password
            ];
            $resetUser = $this->userRepository->updateUser($user->id, $resetData);

            //TODO:SEND RESET MAIL.
            return response()->json([
                'status' => 'success',
                'message' => 'Reset Passsword successful',
                'data' => $resetUser
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => 'failed',
                'message' => $e->getMessage(),
                'data' => []
            ], 500);
        }
    }

    //Change Password
    public function changeAccountPassword(Request $request)
    {
        try {
            $user = Auth::user();
            $oldPassword = $request->oldPassword;
            $newPassword = Hash::make($request->newPassword);

            //Confirm Old Password
            $credentails = ["email" => $user->email, "password" => $oldPassword];
            $token = Auth::attempt($credentails);
            if (!$token) {
                return response()->json([
                    'status' => 'failed',
                    'message' => 'Password Incorrect',
                ], 401);
            }

            //Change Password if everything checks out.
            $resetData = [
                "password" => $newPassword
            ];
            $resetUser = $this->userRepository->updateUser($user->id, $resetData);
            return response()->json([
                'status' => 'success',
                'message' => 'Passsword change successful',
                'data' => $resetUser
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => 'failed',
                'message' => $e->getMessage(),
                'data' => []
            ], 500);
        }
    }
}
