import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ApiConfigService } from './api-config.service';

// Type definitions
type TextResponse = { message: string };
/**
* Base CRUD service that targets the provided controller path.
*
* It mirrors the endpoints of CrudBaseApiController<T>:
* GET /api/{controller}
* GET /api/{controller}/{id}
* POST /api/{controller} -> string
* POST /api/{controller}/batch -> string[]
* PUT /api/{controller}
* PUT /api/{controller}/batch -> number
* DELETE /api/{controller}/{id}
* DELETE /api/{controller}/batch -> text summary
* GET /api/{controller}/{id}/exists -> boolean
* GET /api/{controller}/count -> number
*/
@Injectable({ providedIn: 'root' })
export class CrudBaseApiService<T extends object> {
    /**
    * Example: baseUrl = 'https://your-api', controller = 'Customer' →
    * endpoint = 'https://your-api/api/Customer'
    */
    protected readonly endpoint: string;


    /**
    * @param http Angular HttpClient
    * @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl)
    * @param controllerName Controller name (e.g., 'Customer', 'Orders')
    */
    constructor(
        protected http: HttpClient,
        protected apiConfig: ApiConfigService,
        @Inject(String) private controllerName: string
    ) {
        this.endpoint = apiConfig.getApiUrl(this.controllerName);
    }


    /** Optional default headers (JSON). Override in subclasses if needed. */
    protected get jsonHeaders(): HttpHeaders {
        return new HttpHeaders({ 'Content-Type': 'application/json' });
    }


    /** Shared error handler that surfaces useful messages. */
    protected handleError(error: HttpErrorResponse): Observable<never> {
        let msg = 'Unknown error';
        if (error.error instanceof Blob) {
            // In case backend returns text/plain; charset=utf-8 as Blob
            return throwError(() => new Error('Server returned an error blob'));
        }
        if (typeof error.error === 'string') msg = error.error;
        else if (error.error?.message) msg = error.error.message;
        else if (error.message) msg = error.message;
        return throwError(() => new Error(msg));
    }
    /** Prepare the response for the given entity. Override in subclasses if needed. */
    protected prepareResponse(response: T): T {
        // Do nothing by default
        return response;
    }

    /** GET /api/{controller} */
    getAll(): Observable<T[]> {
        return this.http
            .get<T[]>(this.endpoint)
            .pipe(
                map(response => {

                    for (let i = 0; i < response.length; i++) {
                        const element = response[i];
                        response[i] = this.prepareResponse(element);
                    }
                    return response;
                }),
                catchError(err => this.handleError(err)));
    }


    /** GET /api/{controller}/{id} */
    getById(id: string): Observable<T> {
        return this.http
            .get<T>(`${this.endpoint}/${id}`)
            .pipe(
                map(response => this.prepareResponse(response)),
                catchError(err => this.handleError(err)));
    }



    /** POST /api/{controller} -> string */
    create(entity: T): Observable<string> {
        return this.http
            .post<string>(this.endpoint, entity, { headers: this.jsonHeaders })
            .pipe(catchError(err => this.handleError(err)));
    }


    /** POST /api/{controller}/batch -> string[] */
    createRange(entities: T[]): Observable<string[]> {
        return this.http
            .post<string[]>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
            .pipe(catchError(err => this.handleError(err)));
    }


    /** PUT /api/{controller} */
    update(entity: T): Observable<void> {
        return this.http
            .put<void>(this.endpoint, entity, { headers: this.jsonHeaders })
            .pipe(catchError(err => this.handleError(err)));
    }


    /** PUT /api/{controller}/batch -> number (updated count) */
    updateRange(entities: T[]): Observable<number> {
        return this.http
            .put<number>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
            .pipe(catchError(err => this.handleError(err)));
    }


    /** DELETE /api/{controller}/{id} */
    delete(id: string): Observable<void> {
        return this.http
            .delete<void>(`${this.endpoint}/${id}`)
            .pipe(catchError(err => this.handleError(err)));
    }


    /** DELETE /api/{controller}/batch -> text summary */
    deleteRange(ids: string[]): Observable<TextResponse> {
        // API returns a plain text message; map it into a TextResponse for convenience
        return this.http
            .delete(`${this.endpoint}/batch`, {
                body: ids,
                headers: this.jsonHeaders
            })
            .pipe(
                map((response: any) => ({ message: response || 'Batch delete completed' })),
                catchError(err => this.handleError(err))
            );
    }


    /** GET /api/{controller}/{id}/exists -> boolean */
    exists(id: string): Observable<boolean> {
        return this.http
            .get<boolean>(`${this.endpoint}/${id}/exists`)
            .pipe(catchError(err => this.handleError(err)));
    }


    /** GET /api/{controller}/count -> number */
    count(): Observable<number> {
        return this.http
            .get<number>(`${this.endpoint}/count`)
            .pipe(catchError(err => this.handleError(err)));
    }
}