<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Concerns\AuthorizesCompanyAccess;
use App\Http\Controllers\Controller;
use App\Services\DataverseService;
use App\Services\SharePointService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Throwable;

/**
 * Daily Account folder config (cr6f2_dailyfolderconfig).
 *
 * Two kinds of rows live in the same table:
 *   - Global default rows: cr6f2_company empty, cr6f2_isdefault = true.
 *     The standard folder → doc-type mapping, shared by all companies. Read-only here.
 *   - Company rows: cr6f2_company set, cr6f2_isdefault = false. Created by sync(), editable.
 */
class DailyFolderConfigController extends Controller
{
    use AuthorizesCompanyAccess;

    /** OData entity set (collection) name for the Daily Folder Config table. */
    public const ENTITY = 'cr6f2_dailyfolderconfigs';

    /** SharePoint subfolder names that sync() must never create config rows for (lowercase). */
    private const EXCLUDE_FOLDERS = ['pendingprocess'];

    private const SELECT = [
        'cr6f2_dailyfolderconfigid',
        'cr6f2_foldername',
        'cr6f2_folderpath',
        'cr6f2_doctype',
        'cr6f2_subdoctype',
        'cr6f2_ismoving',
        'cr6f2_autosplit',
        'cr6f2_isdefault',
        '_cr6f2_company_value',
    ];

    public function __construct(
        private DataverseService $dv,
        private SharePointService $sp,
    ) {}

    // ─────────────────────────────────────────────────────────────────────
    // GET /api/v1/companies/{id}/da-folder-config
    // ─────────────────────────────────────────────────────────────────────

    public function index(string $id): JsonResponse
    {
        // Guards the tenant boundary AND validates {id} is a UUID before it is
        // interpolated into the OData $filter below (blocks filter injection).
        if ($deny = $this->authorizeCompany($id)) return $deny;

        try {
            $defaults = $this->dv->get(self::ENTITY, [
                'select'  => self::SELECT,
                'filter'  => '_cr6f2_company_value eq null',
                'orderby' => 'cr6f2_foldername asc',
                'top'     => 200,
            ]);
            $rows = $this->dv->get(self::ENTITY, [
                'select'  => self::SELECT,
                'filter'  => "_cr6f2_company_value eq {$id}",
                'orderby' => 'cr6f2_foldername asc',
                'top'     => 500,
            ]);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Failed to load folder config: ' . $e->getMessage()], 502);
        }

        return response()->json([
            'success'  => true,
            'defaults' => array_map([$this, 'mapRow'], $defaults['value'] ?? []),
            'data'     => array_map([$this, 'mapRow'], $rows['value'] ?? []),
        ]);
    }

    // ─────────────────────────────────────────────────────────────────────
    // POST /api/v1/companies/{id}/da-folder-config
    // ─────────────────────────────────────────────────────────────────────

    public function store(Request $request, string $id): JsonResponse
    {
        if ($deny = $this->authorizeCompany($id)) return $deny;

        $data = $request->validate([
            'folders'               => 'required|array|min:1',
            'folders.*.folder_name' => 'required|string|max:255',
            'folders.*.folder_path' => 'nullable|string|max:1000',
        ]);

        $created = 0;
        $errors  = [];
        foreach ($data['folders'] as $f) {
            $name = trim($f['folder_name']);
            try {
                $this->dv->create(self::ENTITY, [
                    'cr6f2_foldername'         => $name,
                    'cr6f2_folderpath'         => trim($f['folder_path'] ?? ''),
                    'cr6f2_isdefault'          => false,
                    'cr6f2_ismoving'           => false,
                    'cr6f2_autosplit'          => false,
                    'cr6f2_Company@odata.bind' => "/cr6f2_companies({$id})",
                ]);
                $created++;
            } catch (Throwable $e) {
                $errors[] = $name;
            }
        }

        return response()->json([
            'success' => empty($errors),
            'created' => $created,
            'errors'  => $errors,
            'message' => "{$created} folder(s) added."
                . (empty($errors) ? '' : ' Failed: ' . implode(', ', $errors)),
        ]);
    }

    // ─────────────────────────────────────────────────────────────────────
    // POST /api/v1/companies/{id}/da-folder-config/sync
    // ─────────────────────────────────────────────────────────────────────

    public function sync(string $id): JsonResponse
    {
        if ($deny = $this->authorizeCompany($id)) return $deny;

        // 1. Resolve the company's Daily Account folder URL.
        try {
            $company = $this->dv->find('cr6f2_companies', $id, ['select' => ['cr6f2_dailyaccountfolder']]);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Company not found.'], 404);
        }
        $daUrl = trim($company['cr6f2_dailyaccountfolder'] ?? '');
        if ($daUrl === '') {
            return response()->json(['success' => false, 'message' => 'Set the Daily Account Folder URL first.'], 422);
        }

        // 2. List the live subfolders from SharePoint.
        try {
            $folders = $this->sp->listSubfolders($daUrl);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Failed to read SharePoint folders: ' . $e->getMessage()], 502);
        }

        // 3. Load existing company rows (to refresh / skip) and global defaults (to pre-fill).
        try {
            $existing = $this->dv->get(self::ENTITY, [
                'select' => ['cr6f2_dailyfolderconfigid', 'cr6f2_foldername', 'cr6f2_folderpath'],
                'filter' => "_cr6f2_company_value eq {$id}",
                'top'    => 500,
            ]);
            $defaults = $this->dv->get(self::ENTITY, [
                'select' => ['cr6f2_foldername', 'cr6f2_doctype', 'cr6f2_subdoctype', 'cr6f2_ismoving'],
                'filter' => '_cr6f2_company_value eq null and cr6f2_isdefault eq true',
                'top'    => 200,
            ]);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Failed to read existing config: ' . $e->getMessage()], 502);
        }

        // Existing rows keyed by lowercased folder name — id + current path so a
        // re-sync can refresh a moved folder's path without touching its mapping.
        $existingByName = [];
        foreach ($existing['value'] ?? [] as $r) {
            $existingByName[strtolower(trim($r['cr6f2_foldername'] ?? ''))] = [
                'id'   => $r['cr6f2_dailyfolderconfigid'] ?? null,
                'path' => trim($r['cr6f2_folderpath'] ?? ''),
            ];
        }
        $defaultMap = [];
        foreach ($defaults['value'] ?? [] as $d) {
            $defaultMap[strtolower(trim($d['cr6f2_foldername'] ?? ''))] = [
                'doctype'    => $d['cr6f2_doctype']    ?? null,
                'subdoctype' => $d['cr6f2_subdoctype'] ?? null,
                'ismoving'   => (bool) ($d['cr6f2_ismoving'] ?? false),
            ];
        }

        // 4. Add new folders and refresh the path of folders already configured.
        //    A re-sync never touches a row's doc-type / move / auto-split mapping —
        //    only its folder path is updated.
        $created = 0;
        $updated = 0;
        $deleted = 0;
        $skipped = 0;
        $errors  = [];
        $seen    = [];   // lowercased names of folders still present in the root
        foreach ($folders as $folder) {
            $name = trim($folder['name'] ?? '');
            $key  = strtolower($name);
            if ($name === '' || in_array($key, self::EXCLUDE_FOLDERS, true)) {
                $skipped++;
                continue;
            }
            $seen[$key] = true;
            $url = trim($folder['url'] ?? '');

            // Already configured — refresh the folder path only if it changed.
            if (isset($existingByName[$key])) {
                $existingRow = $existingByName[$key];
                if ($url !== '' && $existingRow['id'] && $url !== $existingRow['path']) {
                    try {
                        $this->dv->update(self::ENTITY, $existingRow['id'], ['cr6f2_folderpath' => $url]);
                        $updated++;
                    } catch (Throwable $e) {
                        $errors[] = $name;
                    }
                } else {
                    $skipped++;
                }
                continue;
            }

            // New folder — create a company row.
            $payload = [
                'cr6f2_foldername'         => $name,
                'cr6f2_folderpath'         => $url,
                'cr6f2_isdefault'          => false,
                'cr6f2_ismoving'           => false,
                'cr6f2_autosplit'          => false,
                'cr6f2_Company@odata.bind' => "/cr6f2_companies({$id})",
            ];
            if (isset($defaultMap[$key])) {
                $payload['cr6f2_doctype']    = $defaultMap[$key]['doctype'];
                $payload['cr6f2_subdoctype'] = $defaultMap[$key]['subdoctype'];
                $payload['cr6f2_ismoving']   = $defaultMap[$key]['ismoving'];
            }

            try {
                $this->dv->create(self::ENTITY, $payload);
                $created++;
            } catch (Throwable $e) {
                $errors[] = $name;
            }
        }

        // 5. Remove config rows whose folder is gone from the root. Guarded by a
        //    non-empty listing so a transient empty result can never wipe the lot.
        if (!empty($seen)) {
            foreach ($existingByName as $existingKey => $existingRow) {
                if (!isset($seen[$existingKey]) && $existingRow['id']) {
                    try {
                        $this->dv->delete(self::ENTITY, $existingRow['id']);
                        $deleted++;
                    } catch (Throwable $e) {
                        $errors[] = $existingKey;
                    }
                }
            }
        }

        return response()->json([
            'success' => empty($errors),
            'created' => $created,
            'updated' => $updated,
            'deleted' => $deleted,
            'skipped' => $skipped,
            'errors'  => $errors,
            'message' => "Sync complete — {$created} added, {$updated} updated, {$deleted} removed, {$skipped} skipped."
                . (empty($errors) ? '' : ' Failed: ' . implode(', ', $errors)),
        ]);
    }

    // ─────────────────────────────────────────────────────────────────────
    // PATCH /api/v1/da-folder-config/{recId}
    // ─────────────────────────────────────────────────────────────────────

    public function update(Request $request, string $recId): JsonResponse
    {
        $data = $request->validate([
            'doc_type'     => 'nullable|string|max:100',
            'sub_doc_type' => 'nullable|string|max:100',
            'is_moving'    => 'nullable|boolean',
            'auto_split'   => 'nullable|boolean',
        ]);

        // Global default rows (no company) are read-only here.
        try {
            $rec = $this->dv->find(self::ENTITY, $recId, ['select' => ['_cr6f2_company_value']]);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Config row not found.'], 404);
        }
        if (empty($rec['_cr6f2_company_value'])) {
            return response()->json(['success' => false, 'message' => 'Global default rows cannot be edited here.'], 403);
        }
        if ($deny = $this->authorizeCompany($rec['_cr6f2_company_value'])) return $deny;

        $patch = [];
        if ($request->has('doc_type')) {
            $v = trim($data['doc_type'] ?? '');
            $patch['cr6f2_doctype'] = $v !== '' ? $v : null;
        }
        if ($request->has('sub_doc_type')) {
            $v = trim($data['sub_doc_type'] ?? '');
            $patch['cr6f2_subdoctype'] = $v !== '' ? $v : null;
        }
        if ($request->has('is_moving')) {
            $patch['cr6f2_ismoving'] = (bool) $data['is_moving'];
        }
        if ($request->has('auto_split')) {
            $patch['cr6f2_autosplit'] = (bool) $data['auto_split'];
        }

        if (empty($patch)) {
            return response()->json(['success' => true, 'message' => 'Nothing to update.']);
        }

        try {
            $this->dv->update(self::ENTITY, $recId, $patch);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Failed to update: ' . $e->getMessage()], 502);
        }

        return response()->json(['success' => true, 'message' => 'Folder config updated.']);
    }

    // ─────────────────────────────────────────────────────────────────────
    // DELETE /api/v1/da-folder-config/{recId}
    // ─────────────────────────────────────────────────────────────────────

    public function destroy(string $recId): JsonResponse
    {
        try {
            $rec = $this->dv->find(self::ENTITY, $recId, ['select' => ['_cr6f2_company_value']]);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Config row not found.'], 404);
        }
        if (empty($rec['_cr6f2_company_value'])) {
            return response()->json(['success' => false, 'message' => 'Global default rows cannot be deleted here.'], 403);
        }
        if ($deny = $this->authorizeCompany($rec['_cr6f2_company_value'])) return $deny;

        try {
            $this->dv->delete(self::ENTITY, $recId);
        } catch (Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Failed to delete: ' . $e->getMessage()], 502);
        }

        return response()->json(['success' => true, 'message' => 'Folder config removed.']);
    }

    // ─────────────────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────────────────

    private function mapRow(array $r): array
    {
        return [
            'id'           => $r['cr6f2_dailyfolderconfigid'] ?? null,
            'folder_name'  => $r['cr6f2_foldername']          ?? null,
            'folder_path'  => $r['cr6f2_folderpath']          ?? null,
            'doc_type'     => $r['cr6f2_doctype']             ?? null,
            'sub_doc_type' => $r['cr6f2_subdoctype']          ?? null,
            'is_moving'    => $r['cr6f2_ismoving']            ?? false,
            'auto_split'   => $r['cr6f2_autosplit']           ?? false,
            'is_default'   => $r['cr6f2_isdefault']           ?? false,
            'company_id'   => $r['_cr6f2_company_value']      ?? null,
        ];
    }
}